ASHX Image Download on Click - c#

I am using ashx to serve images from a database, is there anyway to have a user click on a link that allows them to download the file on the computer. (IE it shows the Save Dialog) Like you download a file. Is this possible to do?

If you want it to prompt to save make sure you add the following line when creating the response:
context.Response.AppendHeader("Content-Disposition",
"attachment;filename=" + filename);
This will make the browser treat it like an attachment and prompt with the save dialog.
EDIT: Based on your comment make sure you are building your response correctly:
// set attachment header like above
// then you need to get your file in byte[] form
byte[] dataYouWantToServeUp = GetData();
// you can set content type as well
yourHttpContext.Response.ContentType = "image/jpg";
// serve up the response
yourHttpContext.Response.BinaryWrite(dataYouWantToServeUp);

I think that if the navigator is able to open type of the file you're trying to get, It will open it without asking. If for instants, the file is in zip format, the navigator is not able to open it and will ask you to download it.

Related

Can you use "inline" content-disposition with "application/octet-stream"?

I need the browser to open file types it understands directly in the browser (i.e. no "Open/Save/Cancel" dialog.
Here's my code, which currently works great!...except that every file pops up the dialog box and doesn't directly open the file:
string filePath = Path.Combine(WebConfigurationManager.AppSettings["NewsAttachmentPath"], context.Request.QueryString["FileName"]);
byte[] bytes = System.IO.File.ReadAllBytes(filePath);
context.Response.Clear();
context.Response.ContentType = "application/octet-stream";
context.Response.Cache.SetCacheability(HttpCacheability.Private);
context.Response.Expires = -1;
context.Response.Buffer = true;
context.Response.AddHeader("Content-Disposition", string.Format("{0};FileName=\"{1}\"", "inline", context.Request.QueryString["FileName"]));
context.Response.BinaryWrite(bytes);
context.Response.End();
As you can see, even when I change the Content-Disposition to "inline", it still prompts for download. This is with files that I know my browser understands. In other words, I can go to some random site and click a PDF, and it will open in the browser. My site will make me save it in order to view it.
Pre-emptive answer to "why do you wanna use application/octet-stream?" Because I don't want to create a handler for each single file type. If this is mistaken, please let me know.
You do not need to create a handler per file type. You just change the line:
context.Response.ContentType = "application/octet-stream";
to be:
string contentType = //your logic here, possibly many lines in a separate method
context.Response.ContentType = contentType;
But no: you can't "inline" an application/octet-stream. That means "here's some bytes, but I don't know what they are". The browser can't do much with that, other than save it somewhere, hence a download prompt. You can use content-disposition to suggest a filename, though.
The browser does not work on file extensions - it works on content-type. So: you need to report the correct content-type in your response. This might mean writing a switch / lookup based on the file extension that you know, or it might mean storing the explicit content-type separately as metadata along with the file information.

Displaying pdf files in a web page from a sql database directly without needing to save them to the server file system

I'm currently using an html embed tag to display a pdf file that is saved on the local server. Is there a wayo to display a pdf file on my page without having to save it to the local file system of the server? I just wand to pass it to the view from the controller in such a way that it can be displayed as a pdf in the page without having it stored on the file system directly.
Alternatively, is there a way to call a method to delete the pdf file from the server once the user has navigated away from the page they are viewing? How do I tell if th euser has navicated away from the page and how do i cause that to trigger a method that will delete the file?
I created a MVC class called PdfResult that returns a byte array as a PDF file.
The purpose is as follows (can't upload the source code, sorry):
PdfResult inherits from FileStreamResult
Set the Content-Type header to application/pdf
Set the Content-Disposition to either attachment or inline, and set an appropriate file name
Convert your data to a Stream -- if your data is a byte array, then write it to a MemoryStream.
See https://stackoverflow.com/a/16673120/272072 for a good example of how to do this.
Then, your embed code just needs to point to the action method, as if it was a PDF file.
Here's an example:
public ActionResult ShowPdf() {
// Note: the view should contain a tag like <embed src='MyController/GetPdf'>
return View();
}
public ActionResult GetPdf() {
byte[] pdfBytes = dataRepo.GetPdf(...);
return new PdfResult(pdfBytes, "Filename.pdf", false) ;
}
Here is a link to a CodeProject article and code sample titled Download and Upload Images from SQL Server via ASP.NET MVC. This gives an example of an efficient method to stream content to and from SQL Server via MVC.
You can easily adapt the code to stream your PDF file downloads.
UPDATE
The article uses a DataReader, but it can easily be adapted to Linq2Sql or EF. As an example, here is the Read method where I am reading from the database and copying to the stream:
public override int Read(byte[] buffer, int offset, int count)
{
result = _attachments.ExecuteStoreQuery<byte[]>(
"SELECT SUBSTRING(AttachmentBytes, " + position.ToString() +
", " + count.ToString() + ") FROM Attachments WHERE Id = {0}",
id).First();
var bytesRead = result.Length;
Buffer.BlockCopy(result, 0, buffer, 0, bytesRead);
position += bytesRead;
return (int)bytesRead;
}
You can read the PDF as a bytestream from the database and save it to the http response stream. If you have set the content type correctly to application/pdf, then the browser will load the document in the PDF plugin.
Update (14/Oct/2011): You need to write the bytestream to the Response.OutputStream object. How you create and write the byte stream is dependent on how you have stored in the database and how you are retrieving it. The following code snippet is from an article we have on our website - Generate PDF Forms In ASP.NET Using PDFOne .NET v3.
// Get the page's output stream ready
Response.Clear();
Response.BufferOutput = true;
// Make the browser display the forms document
// using a PDF plug-in. (If no plug in is available,
// the browser will show the File -> Save As dialog box.
Response.ContentType = "application/pdf";
// Write the forms document to the browser
doc.Save(Response.OutputStream);
doc.Close();
doc.Dispose();
The doc object is from our component. You need not use that. This code snippet is only for your understanding. For your requirement, you may have to something like bytestream.save(Response.OutputStream) I guess. BTW, this code is for ordinary ASP.NET, not MVC.
DISCLAIMER: I work for Gnostice.
If you want to create the PDF 100% dynamically, you would generate it completely in memory then stream it out directly to the requesting web browser without saving it as a file. This is very easy to do with the right tools. I would recommend AspPDF from Persits.com as a way to do this very easily. Take a look at their online documentation to see how simple this is to do without creating a bunch of rendered PDF files all over your server.
If you cannot do something like that, then simply incorporate a process to cleanup your "expired" PDF files from your server's filesystem based on their age. For example, after you have created your local PDF file, you just look through the folder containing your temporary PDF's and delete any you find over a certain age. You cannot reliably tell if or when a user has navigated away from your page or site.
For the first part of your question, like mentioned in the comments, use some type of stream object to pass the PDF data around. Right now, you are streaming the file to the local file system, then streaming it once again to the embedded tag for display. Just do away with the intermediate step of saving to the file system, and do the whole thing in memory (although, that's not really a model of efficiency, and might not scale well).
Regarding the second part of your question, that's not as straightforward. MVC really has no concept of state (viewstate, etc.), so it doesn't have events that can be fired from a state change (say, navigating away from a page).
You could use Javascript to detect a user navigating away from your page (windows.onunload), that calls a (C#/VB) method to remove the file from the file system. You would probably have to use AJAX to communicate back to the server, using an HTTP POST method, and have something listening at that URL endpoint to fire your method that removes the file.

ASP.NET - Sending a PDF to the user

I have a process that creates a PDF. I want these PDF's to be temporary and short lived. I want to be able to perform the following when the user clicks a button:
string CreatePDF()//returns fileName.pdf
PromptUserToDownloadPDF()
DeletePDF(fileName.pdf)
I want to avoid having to create a cleanup procedure and deal with any race conditions that arise from users concurrently creating PDF's while running cleanup.
In winforms, I would synchronously prompt a user to download a file. How can I do a similar task in web?
UPDATE
Please note that I am using a 3rd party app to create the PDF's (Apache FOP). Basically I (will) have a function that invokes the command line:
C:>fop "inputfile" "output.pdf"
So, in memory is not an option...that is unless I could somehow do like....
string CreatePDF()//returns fileName.pdf
string RecreatePDFInMemory()
DeletePDF(fileName.pdf)
PromptUserToDownloadPDF()
Something like this:
byte[] _pdfbytes = CreatePDF();
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Length", _pdfbytes.Length.ToString());
Response.BinaryWrite(_pdfbytes);
Since this creates the PDF in memory, you don't need to worry about cleanup.
Edit for OP's edit:
From within CreatePDF, You can use Path.GetTempFileName to create a temp file and execute "fop" to write to that file. Delete that file immediately before returning the byte[]. I recommend doing this delete inside of a finally block. However, "Fop" does support having its output piped to stdout. Having the CreatePDF function grab that is probably cleaner.
Look into doing something along these lines.
Similar to what someone referred to in a different answer, you don't need to save the PDF file on your system, you can just send it as a response.
I'm not sure how you're creating your PDF, but try looking into this below and seeing if your process could use something like this.
HttpResponse currentResponse = HttpContext.Current.Response;
currentResponse.Clear();
currentResponse.ClearHeaders();
currentResponse.ContentType = "application/pdf";
currentResponse.AppendHeader("Content-Disposition", "attachment; filename=my.pdf");
//create the "my.pdf" here
currentResponse.Flush();
currentResponse.End();
Not sure of your process but you should be able to write the PDF to a byte[] and skip writing to the disk altogether.
byte[] pdf = GetPDFBytes(filename)
MemoryStream pdfStream = new MemoryStream(pdf);
Then use the pdfStream to send back to a user.
You can stream out a file with an asp.net page.
I tried to find very old article for you which demonstrates this with a GIF (there's not an actual file)
http://www.informit.com/articles/article.aspx?p=25487
It makes a special page which streams out the data (sets content type appropriately in the header).
Similarly, you can make a "page" to stream out the PDF - it might not even need to ever reside on disk, but if it did, you could delete it after streaming it to the browser.

Remote file download using .NET

I'm trying to download remote file using C# (ASP.NET). The problem is when I browse to the file download URL - it downloads perfectly. When I try the WebClient.DownloadData(url) I get "no data to show" response.
If I browse using the built-in VS2010 browser I still get this "error" message.
The file link is: http://www.tase.co.il/TASE/Pages/Export.aspx?sn=he-IL_ds&enumTblType=AllSecurities&Columns=he-IL_Columns&Titles=he-IL_Titles&TblId=0&ExportType=3
(CSV file)
How can the file be downloaded? any ideas?
Many Thanks
This isn't a download problem on your side, that page is referencing something, probably from session...so you would need to login to the site, get a session, basically go through whatever steps you normally would in route to getting the file so it's generated correctly/available.
If I had to take a guess, I'm thinking they do the export to the file system when you ask for it, so it's a temp file somewhere...not something you can just grab, so unless you went through the step that created it (doesn't seem this Export.aspx page does it), it won't be there, and their error message for this is "No data to show".
There's also the possibility they're explicitly blocking you to prevent any kind of bots/leeching from happening...basically preventing exactly what you're trying to do.
Many sites will block downloads based on the Referer or User-Agent header.
Watch a "working" download with
Fiddler
Watch your code's
download with Fiddler
Compare the
two requests
Update your request
to match the "working" case.
I solved the problem by obtaining the remote file as a stream. From that point I was able to read/write the stream to the response.
//The absolute path to your file
string remoteFile = "http://my.cloudfront.net/videos/vehicle_english_v3.mp4";
//The name of the file you want the user to see when they download the file
int pos = remoteFile.LastIndexOf("/") + 1;
string fileName = remoteFile.Substring(pos, remoteFile.Length - pos);
//Obtain your file as a stream
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(remoteFile);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream fileStream = response.GetResponseStream();
//Write the stream to the response
if (fileStream != null && fileStream.CanRead) {
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
context.Response.ContentType = "application/octet-stream";
context.Response.ClearContent();
fileStream.CopyTo(context.Response.OutputStream);
}

How do I print an existing PDF from a code-behind page?

I've seen a few questions like this around SO but couldn't find anything that's right for me. The chain of events that I would like to occur is as follows:
User clicks an ASP.NET button control
This fires that button's onclick event, which is a function foo() in the C# codebehind
foo() calls some other (unimportant) function which creates a PDF which ends up saved to the server's disk. That function returns the path to the PDF
Without any other user interaction, once the PDF is generated, the print dialog box opens in the user's browser to print that PDF
What do I need to do to accomplish step 4? Ideally, it would be something I can call in foo(), passing in the path to the PDF, that will trigger the print dialog box in the user's browser (printing the PDF and not the page the from which the onclick fired).
I think that I might be able to forward to the URL of the PDF document, and embed some Javascript in the PDF that automatically prints it, but I would rather not - I don't necessarily want to print the PDF every time it's opened (in a browser). Any other good way to do this?
Response.Clear()
Response.AddHeader("Content-Disposition", "attachment; filename=myfilename.pdf")
Response.ContentType = "application/pdf"
Response.BinaryWrite(ms.ToArray())
Where ms = a memorystream containing your file (you don't have to write it to disk in-between.)
Otherwise if you absolutely have to deal with coming from the hard disk, use:
Response.WriteFile("c:\pathtofile.pdf")
Thanks for the answer Vdex. Here is the iText/C# version.
PdfAction action = new PdfAction();
action.Put(new PdfName("Type"), new PdfName("Action"));
action.Put(new PdfName("S"), new PdfName("Named"));
action.Put(new PdfName("N"), new PdfName("Print"));
PdfReader reader = new PdfReader(ReportFile.FullFilePath(reportFile.FilePath));
PdfStamper stamper = new PdfStamper(reader, Response.OutputStream);
stamper.Writer.CloseStream = false;
stamper.Writer.SetOpenAction(action);
stamper.Close();
I did not try this, but it is an idea:
If you can read querystring parameters in javascript embedded in the PDF, then embed the javascript, and make the print function be conditional on a javascript parameter.
That way, if you redirect to, for instance, yourpdf.pdf?print, it will print, and if it is opened without the print parameter, it behaves as any other normal PDF would do.
If you want to go down the path of printing it as soon as it is opened:
There is a flag that you can insert into the PDF that forces it to open up the print dialog as soon as the PDF is opened. I did this ages ago using the abcpdf component in classic ASP, the code looked something like:
Set oDoc = Server.CreateObject("ABCpdf4.Doc")
oDoc.SetInfo oDoc.Root, "/OpenAction", "<< /Type /Action /S /Named /N /Print >>"
Obviously the code would look different depending on which PDF creation tool you are using...
The solution I arrived at was this:
Create a new ASP.NET web form (I called mine BinaryData.aspx) to serve as a placeholder for the PDF. In the code behind, the only method should be Page_Load, which looks like:
protected void Page_Load(object sender, System.EventArgs e)
{
//Set the appropriate ContentType.
Response.ContentType = "Application/pdf";
Response.AppendHeader("Pragma", "no-cache");
Response.AppendHeader("Cache-Control", "no-cache");
//Get the physical path to the file.
string FilePath = (string)Session["fileLocation"];
if ( FilePath != null )
{
string FileName = Path.GetFileName(FilePath);
Response.AppendHeader("Content-Disposition", "attachment; filename="+FileName);
//Write the file directly to the HTTP content output stream.
Response.WriteFile(FilePath);
Response.End();
}
}
The PDF is passed in to the page through the Session variable named "fileLocation". So, all I have to is set that variable, and then call Response.Redirect("BinaryData.aspx").
It doesn't automatically print, but it triggers the download of the PDF without leaving the current page (which is good enough for me).

Categories

Resources