1

I'm working on a ASP.NET (3.5) website that contains a treeview; the nodes' values are set to a filepath (on the server) of a PDF file. When the user clicks a treenode, the server-side code gets the node value (file path), creates a FileInfo object, and (after setting all the header Content-type, Cache-Control, etc. correctly) calls Response.TransmitFile(xxxxpath) to send to the user's browser.

This works fine on the major browsers, on major devices (PCs, Macs, iOS devices). The file downloads correctly and opens on the user's machine. But on certain devices and certain browsers, the PDF file does not open. On Android devices, it appears that Firefox downloads and opens the PDFs correctly, but the stock Android browser does not. On a Kindle Fire, it appears the Silk browser downloads the file successfully, but when trying to open it, I see an error: "PDF trailer not found"....or it says the PDF is DRM-protected (which it is not). I haven't tried another browser (if there is one) on the Fire.

I've experimented using anchor links in static HTML markup, and the problem browsers appear to download and display the PDFs correctly when accessed this way. There seems to be an issue (inconsistency?) with the way ASP.NET sends the response to the browser when done via code. I've used Response.Flush, Response.WriteFile, Response.BinaryWrite, Response.Close, Response.End, etc., and they all produce the same result: MOST browsers handle the file, but SOME cannot handle the PDF.

So, is there some issue with the way ASP.NET constructs the Response object (especially when sending back PDFs) that some browsers don't like? TIA.

TheDudeDude
  • 123
  • 1
  • 4
  • 15

1 Answers1

1

Quite simply, the answer to your question is "No." You may want to post your code if you have doubts about whether or not you're doing it correctly; otherwise: 'no'. :)

I would think the browsers you mentioned are much more suspect than something as simple and established as writing to the Response stream.

For reference here is a tried and true way I do it using iHttpHandler:

    public void ProcessRequest(System.Web.HttpContext context)
    {
        string sFilePath = context.Server.MapPath(String.Concat("~/App_LocalResources", context.Request.QueryString["path"]));
        try
        {
            context.Response.Clear();
            context.Response.ContentType = "application/octet-stream";

            int iReadLength = 16384;
            int iLastReadLength = iReadLength;
            byte[] buffer = new byte[iReadLength];
            if (System.IO.File.Exists(sFilePath))
            {
                System.IO.FileInfo fInfo = new System.IO.FileInfo(sFilePath);
                context.Response.AddHeader("Content-Length", fInfo.Length.ToString());
                context.Response.AddHeader("Content-Disposition", String.Format("attachment; filename=\"{0}\"", fInfo.Name));
                using (System.IO.FileStream input = new System.IO.FileStream(sFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    try
                    {
                        while (iLastReadLength > 0 && context.Response.IsClientConnected)
                        {
                            iLastReadLength = input.Read(buffer, 0, iLastReadLength);

                            if (iLastReadLength > 0)
                            {
                                context.Response.OutputStream.Write(buffer, 0, iLastReadLength);
                                context.Response.OutputStream.Flush();
                            }
                        }
                        context.Response.Flush();
                    }
                    catch
                    {
                    }
                    finally
                    {
                        input.Close();
                    }
                }
            }
        }
        catch
        {
        }
        finally
        {
        }
    }

Since you've indicated you are pulling the file from another place, here is how to write that to a memory stream. Just pull from your Response Stream from the remote server (or File Stream, Sql Binary Reader, w/e really), then reset your MemoryStream position and then use the functionality above to write to your Response stream to the client.

int iReadLength = 16384;
long lLastReadLength = iReadLength;
long lDataIndex = 0;
byte[] buffer = new byte[iReadLength];
using (System.IO.MemoryStream msTemp = new System.IO.MemoryStream())
{
    while (lLastReadLength > 0)
    {
        lLastReadLength = reader.GetBytes(0, lDataIndex, buffer, 0, iReadLength);
        lDataIndex += lLastReadLength;

        if (lLastReadLength > 0)
        {
            msTemp.Write(buffer, 0, Convert.ToInt32(lLastReadLength));
            msTemp.Flush();
        }
    }

    // Reset Memory Position
    msTemp.Position = 0;

    // Now write to the Response Stream here
}
Lawrence Johnson
  • 3,924
  • 2
  • 17
  • 30
  • I'm really stuck. I used your code (I didn't use an HttpHandler, but otherwise the code was intact), with the same results. I've tried over a dozen different code approaches, although they're all variations on sending the file's back to the browser via Response. I'm ready to throw in the towel and tell folks to use Firefox if they can't download the files initially. And if any of my co-workers don't like it they can come up with the solution themselves! Thanks for the code and info. – TheDudeDude Oct 03 '12 at 00:54
  • The only thing about not using a handler is that you'll need to call Response.End() after writing everything to the response stream (in my example just after the finally block). Otherwise .aspx pages may send more stuff to the browser that could break it. And calling Response.End is never fun because it's a real pain to avoid 'Thread was being aborted' errors when you do that. – Lawrence Johnson Oct 03 '12 at 01:24
  • Oh, I've used Response.End, Response.Close, and many other combinations of commands (and just catching the ThreadAbort exceptions), still no dice. The only other 'oddity' in this site's setup is that the files linked to (on the page) reside on a different server than the web server. In the server-side code, I'm using the UNC name to retrieve the PDF, then sending it back via Response. They are not direct links (www.mysite.com/pubs/mypdf.pdf). Do you think it's possible that because my files have to be retrieved by the server, the server's sending some additional info with the byte array? – TheDudeDude Oct 04 '12 at 01:03
  • Yes, that's an interesting detail. Are you saving the incoming file to a MemoryStream or what are you doing with it? – Lawrence Johnson Oct 04 '12 at 03:27
  • Well, I'm defining the stream as the Response.OutputStream, and after I have the file's bytes, I do a stream.Write(), then Response.Flush() (and as I said, various other writes/flushes/ends/closes/etc.). I'm thinking now there might be some issues (Javascript-related maybe) with the ASP.NET TreeView. I know if I set a node's NavigateUrl directly to a UNC file path, IE handles the file OK, but Firefox blows up and says 'Bad Request. – TheDudeDude Oct 04 '12 at 23:09
  • I agree on suspecting the browsers. This problem sounds like it might have the same cause as a few other open questions on S/O. See my answer here: http://stackoverflow.com/a/25418934/665376 – Kimberly Aug 21 '14 at 05:43
  • I was curious to see related answers, so I clicked through and all I got was an answer that links to other answers. – Lawrence Johnson Aug 22 '14 at 06:47