PDF from content of asp.net page - asp.net

I'm trying to create a pdf of the content on a page ("returnsPage.aspx?id="returnId) and allow the user to download this directly when clicking the button.
However in my onClick method I have the following code:
lnkLoadPDF.CommandArgument = "/returns/returnsPage.aspx?id="+returnId.ToString();
string virtualPath = lnkLoadPDF.CommandArgument;
string fileName = System.IO.Path.GetFileName(virtualPath);
Response.Clear();
Response.AddHeader("content-disposition", "attachment; filename=" + fileName);
Response.WriteFile(virtualPath);
Response.ContentType = "";
Response.End();
Response.Redirect("/returns/returnsPage.aspx?id="+returnId);
which returns this error:
'/returns/returnsPage.aspx?id=23' is not a valid virtual path.
Can anyone please tell me what I'm doing wrong?
Thanks!

In order to turn a webpage into a pdf, you must convert it to pdf on the server. In order to do that, you must have a program on the server that can do that for you.
I've tried a variety of webpage-to-pdf converters and one of the better ones is a free, open source program called wkhtmltopdf.
After you create the pdf, you can either redirect the user to the newly created pdf (discouraged), or prompt them to download it with a savefile dialog.
If you get stuck, just search for wkhtmltopdf on stackoverflow or post another question.

You can't send a file to the client and redirect him to a new location during the same request. You also can't create a PDF from a webpage without some kind of component that converts the HTML into a PDF, it's (quite a bit) more tricky that what I think you're trying to attempt.
As for your exception, are you sure returnsPage.aspx exists? :)

Related

Displaying PDF from Memory Stream to browser

I am trying to display a pdf created dynamically and display it in the browser, but I am getting the below characters( a whole lot of them)
Im using iTextSharp
\�(l�x�)�(�)���g���29��2�`C�B�Wa���[�(�o��x��3�J :k��v�os�R
The code which I used to create the pdf does work when sending it by email as an attachment.
Maybe im wrong on displaying it ?
....
doc.Close();
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "attachment;filename=labtest.pdf");
Response.Buffer = true;
stream2.WriteTo(Response.OutputStream);
Response.End();
code above is in a controller method
Answered in comments of question.
Credit to Chris Haas
AJAX in its general form is text-based which is why you are seeing a
text-representation of a PDF. You need to switch to binary processing
if you want to work with binary data but that's a whole different
question. See this for an intro html5rocks.com/en/tutorials/file/xhr2

How to designate the name of a file that a user downloads via an ASHX downloader?

This is my first time writing code that allows a user to download a file uploaded by another user.
I've written an ASHX file, download.ashx, with code that looks like this:
s = context.Request.QueryString.ToString();
byte[] buffer = new ReplacementTicketFileIO().GetSpecifiedFile(s);
context.Response.BinaryWrite(buffer);
context.Response.Flush();
context.Response.End();
When a user clicks on a link to download.ashx with the appropriate querystring, the file is downloaded, but the browser wants to display the content in the browser window. If the user right-clicks on the link, he can download the file, but the name of the file defaults to download.ashx.
I would like to accomplish two things:
1) I would like to be able to specify the default name of the file downloaded on the user's device based on the querystring.
For instance, if the user clicks on download.ashx?linkedfile=car.pdf, I would like for the browser to default to car.pdf for the name of this file.
2) I would like for the browser to default to saving the link, as opposed to opening the link in the browser window.
Is it reasonable for me to want to do this, or is there a better way to download files? Please let me know.
Set the Content-Disposition HTTP header. E.g.
Content-Disposition: attachment; filename=hello.jpg
You can do that in C# using:
Response.AddHeader("Content-Disposition", "attachment; filename=hello.jpg");
Here is something I have for excel files and I believe it forces a download rather than a new window. There is a page property for QueryString. You would just need to capture the QueryString and use it in this code as well as determining the content type. The String.Format will give you clean code.
private string _ExcelFilename
{
get
{
return (Request.QueryString["xls"] != null) ? Request.QueryString"xls"] : "bis";
}
}
Page.Response.Clear();
Page.EnableViewState = false;
Page.Response.Clear();
Page.Response.ContentType = "application/vnd.ms-excel";
Page.Response.AppendHeader("Content-Disposition", String.Format("attachment; filename={0}_{1}.xls", _ExcelFilename, DateTime.Now.ToString("yyyyMMdd")));
Page.Response.Write(excel);
Page.Response.Flush();
Page.Response.End();

How do I send a binary blob to a client browser?

Pardon the dumb newbie question here; web programming isn't my forte... (blush)
I have an aspx page running on a web server. I have a blob (byte array) containing any kind of binary file, plus a file name.
I would like to push this file to be downloaded through the browser onto the client, and opened using whatever application is default for this file type. I really don't want to save the blob as a file on the server; that will leave a terrible housekeeping mess that I just don't want to think about.
I did try googling this question, but I guess I'm using the wrong keywords.
This really should be obvious how to do it, but I'm having no joy.
What is the trick?
Thanks!
Response.BinaryWrite(byteArray);
You should also set the content type
Response.ContentType = "application/pdf";
But that will be based on your file type.
And the file name (and everything together) is done like this
Response.AddHeader("content-disposition",
String.Format("attachment;filename={0}", fileName));
Response.ContentType = "application/pdf";
Response.BinaryWrite(byteArray);
First, you have to know the mime type. Once you know that, you can set the Response.ContentType property. After that, just use Response.BinaryWrite(). If you don't first set the ContentType property, the client will have almost no chance of opening the file correctly.

Zip Folder is always corrupted after downloaded

For some reason when I download a zip folder from my server's folder it is always corrupted. Here is the code:
protected void gvFiles_RowCommand(object sender, System.Web.UI.WebControls.GridViewCommandEventArgs e)
{
string fileUrl = String.Empty;
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/zip";
Response.WriteFile(fileUrl);
Response.End();
}
}
And here is how the GridView is populated:
private void BindData()
{
List<SampleFile> files = new List<SampleFile>();
for(int i=1;i<=3;i++)
{
SampleFile sampleFile = new SampleFile();
sampleFile.Name = "File " + i;
sampleFile.Url = Server.MapPath("~/Files/File"+i+".txt");
files.Add(sampleFile);
}
SampleFile file = new SampleFile();
file.Name = "Zip File";
file.Url = Server.MapPath("~/Files/WebSiteNestedMasters.zip");
files.Add(file);
gvFiles.DataSource = files;
gvFiles.DataBind();
}
I don't know asp.net at all, but this is pretty commonly a result of doing the download in a text mode instead of binary mode. Line ending characters get converted from \r\n to \n or vice versa, and everything goes nuts.
As #lassevk suggested you should download the corrupted zip file and compare it with the original file on the server. Are both the same length? Use a hex editor to inspect the contents of the file. In this related thread you are saying that if you point the browser directly to the zip file it is also corrupted, meaning that the problem is probably not related to the headers but something wrong with IIS. Are you using some third party ISAPI extensions that could modify the file?
Assuming that you need to do this: (e.g. you are unable to provide a direct link)
<a href='<% Eval("Url")) %>'>download</a>
I'll first make an observation that having a RowCommand handler start to return a file isn't the way to do this. Create some sort of download link to a different page. Separate file downloading from the rest of the page containing the grid.
now ...
You've got the basics about right, but like a comment in this CodeProject tutorial you're soon going to run into issues.
Where the above code sample falls
down:
not responsive to user
the code will keep going and you'll have no
idea if the user actually downloaded
the file (it may not matter to you)
won't work with all browsers
cannot resume a download
doesn't show progress
larger files will use more server memory and take longer to stream
and a lot of downloads will mean the server is going to take a resource hit
whereas you might want ..
works just like a clicked download (ie using get, not post)
works in all browsers on all platforms in the way the user expects
(ie filename hint works on things like
IE for Mac or Netscape 4.1).
show download progress
resumable downloads
tracking all downloads and knowing when downloads complete
looks like a file, even if it isn't
expiration on url.
allows for high concurrent # of downloads for any size of file
Although written in VB .net1.1 the article Tracking and Resuming Large File Downloads in ASP.NET [devx] is far better at explaining how to do it correctly.
Easy code is easy but doesn't always work, and efficiently streaming files to users 100% of the time takes a little more effort than setting a content header and shoveling some bits down the wire.
First, verify that the contents of the file actually looks like a zip file. You can do that simply by dropping the file into notepad, and looking at the contents. If the file starts with PK and some funny characters, it might be a zip file. If it contains HTML, that might be a clue as to why it is going wrong.
Looking at your code, it struck me that you're passing the url to the file to Response.WriteFile. Does Response.WriteFile handle url's or does it use local filenames? Perhaps everything up to Response.WriteFile works, and thus the right headers are sent, etc. but then the code crashes with an exception, which might make your "zip file" contain a HTML error message.
Edit: Ok, first problem-solving step completed. File looks like a zip file in notepad, not a HTML error message.
Next step, since the file resides on the server, try writing an url directly to the file, instead of going through your application. Does that work?
If not, try a different zip program (which one are you using by the way?).
As an example of why you should try the file directly and different unzipping programs. FinalBuilder produces zip files that PowerArchiver complains about, but 7zip does not, so there might be something wrong with either the file or your program, or possibly even both.
But verify that the files are correct, can be opened, both if you don't download them at all, and check what happens if you download them directly outside of your application.
Edit: Ok, you've verified that the file won't open even if you download it directly from your server.
How about opening the file in just explorer, bypassing the web server altogether? For instance, since it looks like you have the file locally, try just navigating to the right folder and opening the file directly. Does that work?
Here is the updated code:
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileStream fs = new FileStream(fileUrl, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, (int) fs.Length);
fs.Close();
Response.Clear();
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/octet-stream";
Response.AppendHeader("content-length", buffer.Length.ToString());
Response.BinaryWrite(buffer);
Response.Flush();
Response.End();
Here is the fiddler stats:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/8.0.0.0
Date: Wed, 17 Dec 2008 22:22:05 GMT
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename=MyZipFolder.zip
Content-Length: 148
Cache-Control: private
Content-Type: application/x-zip-compressed
Connection: Close
I just tried the same code with the .docx file and same result (The file is corrupted): Here is the code:
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileInfo info = new FileInfo(fileUrl);
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.Buffer = true;
Response.AppendHeader("Content-Length",info.Length.ToString());
Response.ContentType = GetContentType(fileUrl);
Response.AppendHeader("Content-Disposition:", "attachment; filename=" + fileName);
Response.TransmitFile(fileUrl);
Response.Flush();
Response.End();
}

"name" web pdf for better default save filename in Acrobat?

My app generates PDFs for user consumption. The "Content-Disposition" http header is set as mentioned here. This is set to "inline; filename=foo.pdf", which should be enough for Acrobat to give "foo.pdf" as the filename when saving the pdf.
However, upon clicking the "Save" button in the browser-embedded Acrobat, the default name to save is not that filename but instead the URL with slashes changed to underscores. Huge and ugly. Is there a way to affect this default filename in Adobe?
There IS a query string in the URLs, and this is non-negotiable. This may be significant, but adding a "&foo=/title.pdf" to the end of the URL doesn't affect the default filename.
Update 2: I've tried both
content-disposition inline; filename=foo.pdf
Content-Type application/pdf; filename=foo.pdf
and
content-disposition inline; filename=foo.pdf
Content-Type application/pdf; name=foo.pdf
(as verified through Firebug) Sadly, neither worked.
A sample url is
/bar/sessions/958d8a22-0/views/1493881172/export?format=application/pdf&no-attachment=true
which translates to a default Acrobat save as filename of
http___localhost_bar_sessions_958d8a22-0_views_1493881172_export_format=application_pdf&no-attachment=true.pdf
Update 3: Julian Reschke brings actual insight and rigor to this case. Please upvote his answer.
This seems to be broken in FF (https://bugzilla.mozilla.org/show_bug.cgi?id=433613) and IE but work in Opera, Safari, and Chrome. http://greenbytes.de/tech/tc2231/#inlwithasciifilenamepdf
Part of the problem is that the relevant RFC 2183 doesn't really state what to do with a disposition type of "inline" and a filename.
Also, as far as I can tell, the only UA that actually uses the filename for type=inline is Firefox (see test case).
Finally, it's not obvious that the plugin API actually makes that information available (maybe someboy familiar with the API can elaborate).
That being said, I have sent a pointer to this question to an Adobe person; maybe the right people will have a look.
Related: see attempt to clarify Content-Disposition in HTTP in draft-reschke-rfc2183-in-http -- this is early work in progress, feedback appreciated.
Update: I have added a test case, which seems to indicate that the Acrobat reader plugin doesn't use the response headers (in Firefox), although the plugin API provides access to them.
Set the file name in ContentType as well. This should solve the problem.
context.Response.ContentType = "application/pdf; name=" + fileName;
// the usual stuff
context.Response.AddHeader("content-disposition", "inline; filename=" + fileName);
After you set content-disposition header, also add content-length header, then use binarywrite to stream the PDF.
context.Response.AddHeader("Content-Length", fileBytes.Length.ToString());
context.Response.BinaryWrite(fileBytes);
Like you, I tried and tried to get this to work. Finally I gave up on this idea, and just opted for a workaround.
I'm using ASP.NET MVC Framework, so I modified my routes for that controller/action to make sure that the served up PDF file is the last part of the location portion of the URI (before the query string), and pass everything else in the query string.
Eg:
Old URI:
http://server/app/report/showpdf?param1=foo&param2=bar&filename=myreport.pdf
New URI:
http://server/app/report/showpdf/myreport.pdf?param1=foo&param2=bar
The resulting header looks exactly like what you've described (content-type is application/pdf, disposition is inline, filename is uselessly part of the header). Acrobat shows it in the browser window (no save as dialog) and the filename that is auto-populated if a user clicks the Acrobat Save button is the report filename.
A few considerations:
In order for the filenames to look decent, they shouldn't have any escaped characters (ie, no spaces, etc)... which is a bit limiting. My filenames are auto-generated in this case, and before had spaces in them, which were showing up as '%20's in the resulting save dialog filename. I just replaced the spaces with underscores, and that worked out.
This is by no names the best solution, but it does work. It also means that you have to have the filename available to make it part of the original URI, which might mess with your program's workflow. If it's currently being generated or retrieved from a database during the server-side call that generates the PDF, you might need to move the code that generates the filename to javascript as part of a form submission or if it comes from a database make it a quick ajax call to get the filename when building the URL that results in the inlined PDF.
If you're taking the filename from a user input on a form, then that should be validated not to contain escaped characters, which will annoy users.
Hope that helps.
Try placing the file name at the end of the URL, before any other parameters. This worked for me.
http://www.setasign.de/support/tips-and-tricks/filename-in-browser-plugin/
In ASP.NET 2.0 change the URL from
http://www. server.com/DocServe.aspx?DocId=XXXXXXX
to
http://www. server.com/DocServe.aspx/MySaveAsFileName?DocId=XXXXXXX
This works for Acrobat 8 and the default SaveAs filename is now MySaveAsFileName.pdf.
However, you have to restrict the allowed characters in MySaveAsFileName (no periods, etc.).
Apache's mod_rewrite can solve this.
I have a web service with an endpoint at /foo/getDoc.service. Of course Acrobat will save files as getDoc.pdf. I added the following lines in apache.conf:
LoadModule RewriteModule modules/mod_rewrite.so
RewriteEngine on
RewriteRule ^/foo/getDoc/(.*)$ /foo/getDoc.service [P,NE]
Now when I request /foo/getDoc/filename.pdf?bar&qux, it gets internally rewritten to /foo/getDoc.service?bar&qux, so I'm hitting the correct endpoint of the web service, but Acrobat thinks it will save my file as filename.pdf.
If you use asp.net, you can control pdf filename through page (url) file name.
As other users wrote, Acrobat is a bit s... when it choose the pdf file name when you press "save" button: it takes the page name, removes the extension and add ".pdf".
So /foo/bar/GetMyPdf.aspx gives GetMyPdf.pdf.
The only solution I found is to manage "dynamic" page names through an asp.net handler:
create a class that implements IHttpHandler
map an handler in web.config bounded to the class
Mapping1: all pages have a common radix (MyDocument_):
<httpHandlers>
<add verb="*" path="MyDocument_*.ashx" type="ITextMiscWeb.MyDocumentHandler"/>
Mapping2: completely free file name (need a folder in path):
<add verb="*" path="/CustomName/*.ashx" type="ITextMiscWeb.MyDocumentHandler"/>
Some tips here (the pdf is dynamically created using iTextSharp):
http://fhtino.blogspot.com/2006/11/how-to-show-or-download-pdf-file-from.html
Instead of attachment you can try inline:
Response.AddHeader("content-disposition", "inline;filename=MyFile.pdf");
I used inline in a previous web application that generated Crystal Reports output into PDF and sent that in browser to the user.
File download dialog (PDF) with save and open option
Points To Remember:
Return Stream with correct array size from service
Read the byte arrary from stream with correct byte length on the basis of stream length.
set correct contenttype
Here is the code for read stream and open the File download dialog for PDF file
private void DownloadSharePointDocument()
{
Uri uriAddress = new Uri("http://hyddlf5187:900/SharePointDownloadService/FulfillmentDownload.svc/GetDocumentByID/1/drmfree/");
HttpWebRequest req = WebRequest.Create(uriAddress) as HttpWebRequest;
// Get response
using (HttpWebResponse httpWebResponse = req.GetResponse() as HttpWebResponse)
{
Stream stream = httpWebResponse.GetResponseStream();
int byteCount = Convert.ToInt32(httpWebResponse.ContentLength);
byte[] Buffer1 = new byte[byteCount];
using (BinaryReader reader = new BinaryReader(stream))
{
Buffer1 = reader.ReadBytes(byteCount);
}
Response.Clear();
Response.ClearHeaders();
// set the content type to PDF
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment;filename=Filename.pdf");
Response.Buffer = true;
Response.BinaryWrite(Buffer1);
Response.Flush();
// Response.End();
}
}
I believe this has already been mentioned in one flavor or another but I'll try and state it in my own words.
Rather than this:
/bar/sessions/958d8a22-0/views/1493881172/export?format=application/pdf&no-attachment=true
I use this:
/bar/sessions/958d8a22-0/views/1493881172/NameThatIWantPDFToBe.pdf?GeneratePDF=1
Rather than having "export" process the request, when a request comes in, I look in the URL for GeneratePDF=1. If found, I run whatever code was running in "export" rather than allowing my system to attempt to search and serve a PDF in the location /bar/sessions/958d8a22-0/views/1493881172/NameThatIWantPDFToBe.pdf. If GeneratePDF is not found in the URL, I simply transmit the file requested. (note that I can't simply redirect to the file requested - or else I'd end up in an endless loop)
You could always have two links. One that opens the document inside the browser, and another to download it (using an incorrect content type). This is what Gmail does.
For anyone still looking at this, I used the solution found here and it worked wonderfully. Thanks Fabrizio!
The way I solved this (with PHP) is as follows:
Suppose your URL is SomeScript.php?id=ID&data=DATA and the file you want to use is TEST.pdf.
Change the URL to SomeScript.php/id/ID/data/DATA/EXT/TEST.pdf.
It's important that the last parameter is the file name you want Adobe to use (the 'EXT' can be about anything). Make sure there are no special chars in the above string, BTW.
Now, at the top of SomeScript.php, add:
$_REQUEST = MakeFriendlyURI( $_SERVER['PHP\_SELF'], $_SERVER['SCRIPT_FILENAME']);
Then add this function to SomeScript.php (or your function library):
function MakeFriendlyURI($URI, $ScriptName) {
/* Need to remove everything up to the script name */
$MyName = '/^.*'.preg_quote(basename($ScriptName)."/", '/').'/';
$Str = preg_replace($MyName,'',$URI);
$RequestArray = array();
/* Breaks down like this
0 1 2 3 4 5
PARAM1/VAL1/PARAM2/VAL2/PARAM3/VAL3
*/
$tmp = explode('/',$Str);
/* Ok so build an associative array with Key->value
This way it can be returned back to $_REQUEST or $_GET
*/
for ($i=0;$i < count($tmp); $i = $i+2){
$RequestArray[$tmp[$i]] = $tmp[$i+1];
}
return $RequestArray;
}//EO MakeFriendlyURI
Now $_REQUEST (or $_GET if you prefer) is accessed like normal $_REQUEST['id'], $_REQUEST['data'], etc.
And Adobe will use your desired file name as the default save as or email info when you send it inline.
I was redirected here because i have the same problem. I also tried Troy Howard's workaround but it is doesn't seem to work.
The approach I did on this one is to NO LONGER use response object to write the file on the fly. Since the PDF is already existing on the server, what i did was to redirect my page pointing to that PDF file. Works great.
http://forums.asp.net/t/143631.aspx
I hope my vague explanation gave you an idea.
Credits to Vivek.
Nginx
location /file.pdf
{
# more_set_headers "Content-Type: application/pdf; name=save_as_file.pdf";
add_header Content-Disposition "inline; filename=save_as_file.pdf";
alias /var/www/file.pdf;
}
Check with
curl -I https://example.com/file.pdf
Firefox 62.0b5 (64-bit): OK.
Chrome 67.0.3396.99 (64-Bit): OK.
IE 11: No comment.
Try this, if your executable is "get.cgi"
http://server,org/get.cgi/filename.pdf?file=filename.pdf
Yes, it's completely insane. There is no file called "filename.pdf" on the server, there is directory at all under the executable get.cgi.
But it seems to work. The server ignores the filename.pdf and the pdf reader ignores the "get.cgi"
Dan

Resources