ASP.NET - Sending a PDF to the user - c#

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.

Related

Why do I need Content/Type variable?

In my MVC Core application, I have in my controller a way for the user to download an image that I have saved which is called from a button click in my view. I don't understand why I need the second variable "contentType" string in my return File(). I can still download the image as a .jpg if I put in "application/pdf" instead of "application/jpg", so what is even the point of having it other than the fact that it is required. What is the point of this variable?
public FileStreamResult Download()
{
string filename = "Capture.JPG";
string filepath = hostingEnvironment.WebRootPath + "\\Image";
string fullName = Path.Combine(filepath, filename);
return File(new FileStream(fullName, FileMode.Open), "application/pdf", filename);
}
Flydog's comment is quite pertinent; perhaps the most useful or obvious difference you'll find in declaring a content type is that the browser may use it to invoke a different behavior depending on the setting. If you send an mp4 video as "video/mp4" the browser may well play it within its viewport. If you send the same file as "application/octet-stream" it may offer a save as. If you claim one content type and it's actually another it's likely the browser will ignore you (note: claiming a pdf is pdf or octet stream are both reasonable/true. Claiming it's a jog is not). Content-Disposition also plays a part in the server aiming to give the browser some direction on what to do with the file..
..but primarily I'd say a good reason to tell the truth about the content type (ie don't claim it's a jpg when it's a pdf) is the interop mantra "when it sones to standards adherence, be strict in what you send and liberal in what you accept" - you're sending, so follow protocol :)

Use PdfReport.Core in a WEB-API .NET CORE 2

I am looking into PdfReport.Core and have been asked to let our .NET CORE 2.0 WEB-API return a PDF to the calling client. The client would be any https caller like a ajax or mvc client.
Below is a bit of the code I am using. I am using swashbuckle to test the api, which looks like it is returning the report but when I try to open in a PDF viewer it says it is curropted. I am thinking I am not actually outputting the actual PDF to the stream, suggestions?
[HttpGet]
[Route("api/v1/pdf")]
public FileResult GetPDF()
{
var outputStream = new MemoryStream();
InMemoryPdfReport.CreateStreamingPdfReport(_hostingEnvironment.WebRootPath, outputStream);
outputStream.Position = 0;
return new FileStreamResult(outputStream, "application/pdf")
{
FileDownloadName = "report.pdf"
};
}
I'm not familiar with that particular library, but generally speaking with streams, file corruption is a result of either 1) the write not being flushed or 2) incorrect positioning within the stream.
Since, you've set the position back to zero, I'm guessing the problem is that your write isn't being flushed correctly. Essentially, when you write to a stream, the data is not necessarily "complete" in the stream. Sometimes writes are queued to more efficiently write in batches. Sometimes, there's cleanup tasks a particular stream writer needs to complete to "finalize" everything. For example, with a format like PDF, end matter may need to be appended to the bytes, particular to the format. A stream writer that is writing PDF would take care of this in a flush operation, since it cannot be completed until all writing is done.
Long and short, review the documentation of the library. In particular, look for any method/process that deals with "flushing". That's most likely what your missing.

Pdf is not getting open some time after BinaryWrite

I created the functionality to edit the existing PDf content(adding some text and images) and after that i am opening this pdf file for print or download and i am using this code
Pdf is not getting open some time after BinaryWrite
byte[] outBuf = outStream1.GetBuffer();
HttpContext.Current.Response.Expires = 0;
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename="test.pdf");
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.ContentEncoding = new System.Text.UTF8Encoding();
HttpContext.Current.Response.BinaryWrite(outBuf);
outStream.Close();
HttpContext.Current.Response.End();
It is working fine on local machine or development server and some time on server.
On server after some times it is opening blank page. And after browser cache clear or temp file clear it works
I am not getting it where is the problem may be some client browser memory problem.
But if it is browser memory issue the it should also come on local too because I am using the same browser.
Please give me some idea or solution so that I can sort our this thing.
The GetBuffer method only returns exactly the content of the memory stream if it was created as a readonly stream from an array of bytes to begin with. Otherwise it returns the internal buffer, which may contain unused bytes at the end.
Use the ToArray method to get exactly the content of the memory stream, and nothing more:
byte[] outBuf = outStream1.ToArray();
Another thing that triggers some browsers some of the time to treat your stream as PDF, is to append some dummy argument to your URL so that it ends with ".pdf". I am not sure if this is really necessary in your case since your are setting the 'content-disposition' to 'attachment'. It won't hurt though.

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.

Reading Stream and open that document

I have a stream object in which I have some document, I know what document is it, how can I open that document without saving it into a file physically. So I want to directlly open the document from stream itself without saving it.
How to do that? I doing it in c#
this is how i do such a thing, but i know the file's name. this is method is called into an empty aspx page (i mean without any kind of html markup except the <#Page ... /> line )
private void LoadAttachment()
{
byte[] ImageData = ... get data from somewhere ...
Response.Buffer = true;
String filename = "itakethis.fromdatabase";
Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
Response.ContentType = GetMimeType(filename);//you can try to extrapolate it from file extension
if(ImageData.Length > 0)
Response.BinaryWrite(ImageData);
else
Response.BinaryWrite(new byte[1]);
Response.Flush();
ApplicationInstance.CompleteRequest();
}
`
You can use MemoryStream to use the document only in RAM.
Unfortunately most programs (Word, Excel, Notepad, ...) need a filename to open a file. So i think there is no real solution to your problem.
The only think would be to write a File System Filter Driver, that returns the MemoryStream for a virtual filename, but that driver has to be written in C++ and is not the easiest one to do.
Why can't you save the file?
I've used GetTempFilename to good effect in the past. I can't remember exactly which class it's in, probably System.Environment or File itself. Anyway, you can be sure the file will be in the system defined temp folder, so you can safely delete the file once you're done with it.

Categories

Resources