Trying to stream a PDF file with asp.net is producing a “damaged file”

0 votes
asked Aug 19, 2009 by pablo

In one of my asp.net web applications I need to hide the location of a pdf file being served to the users.

Thus, I am writing a method that retrieves its binary content from its location on a CMS system and then flushes a byte array to the web user.

I'm getting, unfortunately, an error when downloading the stream: "Could not open the file because it is damadged" (or something similar to that, when opening the file in adobe reader).

Question 1: what am I doing wrong? Question 2: can I download large files using this approach?

    private void StreamFile(IItem documentItem)
    {
        //CMS vendor specific API
        BinaryContent itemBinaryContent = documentItem.getBinaryContent();
        //Plain old .NET
        Stream fileStream = itemBinaryContent.getContentStream();
        var len = itemBinaryContent.getContentLength();
        SendStream(fileStream, len, itemBinaryContent.getContentType());
    }

    private void SendStream(Stream stream, int contentLen, string contentType)
    {
        Response.ClearContent();
        Response.ContentType = contentType;
        Response.AppendHeader("content-Disposition", string.Format("inline;filename=file.pdf"));
        Response.AppendHeader("content-length", contentLen.ToString());
        var bytes = new byte[contentLen];
        stream.Read(bytes, 0, contentLen);
        stream.Close();
        Response.BinaryWrite(bytes);
        Response.Flush();
    }

5 Answers

0 votes
answered Aug 11, 2009 by igor-zelaya

This Snippet did it for me:

                Response.Clear();
                Response.ClearContent();
                Response.ClearHeaders();
                Response.ContentType = mimeType;
                Response.AddHeader("Content-Disposition", "attachment");
                Response.WriteFile(filePath);
                Response.Flush();
0 votes
answered Aug 19, 2009 by jeff-siver

I've got something similar working on a current 2.0 web site. And I remember struggling to get it to work though it's been a while so I don't remember the struggles.

There are, though, a few differences between what I have what you have. Hopefully these will help you solve the problem.

  • After the ClearContent call, I call ClearHeaders();
  • I'm not specifying a length.
  • I'm not specifing inline on the Disposition
  • I know everyone says not to do it, but I have a Response.Flush(); followed by Response.Close();

And, one other thing, check your contentType value to make sure it is correct for PDFs (("Application/pdf").

0 votes
answered Aug 19, 2009 by jjo

Here is a method I use. This passes back an attachment, so IE produces an Open/Save dialog. I also happen to know that the files will not be larger than 1M, so I'm sure there's a cleaner way to do this.

I had a similar problem with PDFs, and I realized that I absolutely had to use Binary streams and ReadBytes. Anything with strings messed it up.

Stream stream = GetStream(); // Assuming you have a method that does this.
BinaryReader reader = new BinaryReader(stream);

HttpResponse response = HttpContext.Current.Response;
response.ContentType = "application/pdf";
response.AddHeader("Content-Disposition", "attachment; filename=file.pdf");
response.ClearContent();
response.OutputStream.Write(reader.ReadBytes(1000000), 0, 1000000);

// End the response to prevent further work by the page processor.
response.End();
0 votes
answered Aug 8, 2010 by extremeswank

The other answers copy the file contents into memory before sending the response. If the data is already in memory, then you will have two copies, which is not very good for scalability. This may work better instead:

public void SendFile(Stream inputStream, long contentLength, string mimeType, string fileName)
{
    string clength = contentLength.ToString(CultureInfo.InvariantCulture);
    HttpResponse response = HttpContext.Current.Response;
    response.ContentType = mimeType;
    response.AddHeader("Content-Disposition", "attachment; filename=" + fileName);
    if (contentLength != -1) response.AddHeader("Content-Length", clength);
    response.ClearContent();
    inputStream.CopyTo(response.OutputStream);
    response.OutputStream.Flush();
    response.End();
}

Since a Stream is a collection of bytes, there is no need to use a BinaryReader. And as long as the input stream ends at the end of the file, then you can just use the CopyTo() method on the stream you want to send to the web browser. All the contents will be written to the target stream, without making any intermediate copies of the data.

If you need to only copy a certain number of bytes from the stream, you can create an extension method that adds a couple more CopyTo() overloads:

public static class Extensions
{
    public static void CopyTo(this Stream inStream, Stream outStream, long length)
    {
        CopyTo(inStream, outStream, length, 4096);
    }

    public static void CopyTo(this Stream inStream, Stream outStream, long length, int blockSize)
    {
        byte[] buffer = new byte[blockSize];
        long currentPosition = 0;

        while (true)
        {
            int read = inStream.Read(buffer, 0, blockSize);
            if (read == 0) break;
            long cPosition = currentPosition + read;
            if (cPosition > length) read = read - Convert.ToInt32(cPosition - length);
            outStream.Write(buffer, 0, read);
            currentPosition += read;
            if (currentPosition >= length) break;
        }
    }
}

You could then use it like so:

inputStream.CopyTo(response.OutputStream, contentLength);

This would work with any input stream, but a quick example would be reading a file from the file system:

string filename = @"C:\dirs.txt";
using (FileStream fs = File.Open(filename, FileMode.Open))
{
    SendFile(fs, fs.Length, "application/octet-stream", filename);
}

As mentioned before, make sure your MIME type is correct for the content.

0 votes
answered Sep 15, 2017 by muteddisk

when you use your code you are copying the bytes from your pdf straight in your response. all the specials ascii codes needs to be encoded to pass in http. When using the stream function the stream is encoded so you don't have to worry about it.

    var bytes = new byte[contentLen];
    stream.Read(bytes, 0, contentLen);
    stream.Close();
    Response.BinaryWrite(bytes);
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...