Tuesday, August 4, 2009

Why wont my Response.WriteFile work through a http proxy ?

Today I've been finally finishing up the literature download functionality for one of our websites. With the dynamic Zip file building sorted using the open source Ionic.Zip component it was just a matter of sending out an e-mail with a unique download link and then serving up the Zip file on demand.

Streaming files in ASP.NET is a topic that is well covered on the Internet and the System.Web.HttpResponse object has an nice easy WriteFile method that takes the full path to a file and returns it to the client. Fine and dandy in testing but when the new functionality was deployed to the live site and a couple of staff had a go, nothing downloaded.

All my testing had been performed either at localhost (IIS7 on Vista Enterprise) or on our development server (IIS6 on Server 2003). The key thing was that all the HTTP traffic had been local. Now the code was on our live website the HTTP traffic was going over the Internet and onto our LAN through our Bloxx proxy server. I had tested the live site succesfully from my PC but I don't use the proxy.

So what was going on. After reconfiguring IE8 for the Bloxx proxy, click the link, the browser window opens but a blank page and a stuck progress bar at the bottom of the page. Turn the proxy off, no problem. Hit the link browser window opens, Open/Save dialog pops up.

So the enevitable Googling starts and I find nothing specfic. Was it the Content-Type of application/octet-stream the problem? Switching that out for application/zip & application/binary was no help. Then I see a web page with something I hadn't seen before; the 'Content-Length' header defining the file size. Compile & commit code, update devel site code, rsync to live site and it works immediately:

Private Sub TransmitFile(ByVal FileName As String)

 Dim objFile As System.IO.FileInfo = New System.IO.FileInfo(FileName)

 If objFile.Exists Then
 
  Dim strTransmitName As String = "download-" & Now.Date.ToShortDateString & "." & objFile.Extension

  '# send file

  Response.Clear()
  Response.BufferOutput = False

  Response.ContentType = "application/octet-stream"
  Response.AppendHeader("Content-Disposition", "attachment; filename=" & strTransmitName)
  Response.AppendHeader("Content-Length", objFile.Length.ToString)

  Response.WriteFile(objFile.FullName)

  HttpContext.Current.ApplicationInstance.CompleteRequest()

 End If

End Sub

The easiest way to get the size of your file for the Content-Length is using the Length property of a System.IO.FileInfo object. This is easy to instantiate with the full path to the file and it also has a handy Exists property so you can test to see if the file is indeed there.

Two further things are worth comment. Firstly the Content-Disposition header gives you a nice easy way of renaming your file as it is streamed back to the browser. Our files are named with GUIDs which we resuse as the download ID. Great for identifying a file in code on the server, not so good for finding the file again when you have downloaded it to your PC. We rename the file with our company name and the date but in this code I've created a name using the word 'download' and the date.

Secondly there is some controversy over the method to end your HTTP stream. Response.End() is the obvious choice but does nasty things to the current thread and fires a ThreadAbortion exception. This is discussed on Rick Strahl's Blog and there doesn't appear to be a fully right answer. Calling HttpContext.Current.ApplicationInstance.CompleteRequest() works for me in this situation but Rick's post and the subsequent comments make interesting reading on alternatives.

No comments:

Post a Comment