Programatically Download File from Remote Location to User Through Server
- 2
- Add a Comment
I struggled with this problem for the better part of a day. The problem is this: I had a Page that needed to push a ZIP file out to a User, programatically, based on information acquired from SQL. Normally this would be fairly simple, but the file isn’t on the web server, and it can’t be saved on the web server due to size constraints.
The User doesn’t have access to the server upon which the file is stored, so I can’t simply add a hyperlink. The file can exceed 30GB in size, so I can’t do a Response.WriteFile. I am required to provide the user an on-demand download, so I can’t copy the 30GB file to the webserver when he asks for it, then pass it over to him.
No, I must open two streams, and buffer the file in such a way that the file goes through the web server to get to the User.
I could have registered a startup script that would response.redirect to the file after the next postback, but the site uses AJAX. AJAX-Enabled websites cannot use Response.Redirect or Server.Transfer functionality due to the way partial-page postbacks affect the client javascript codebase.
Additionally, I could create another HttpWebResponse object, but I couldn’t redirect the User’s Response to the instantiated one. However, if I could take over that response entirely, I could just use it to transfer the file. So what I needed was an HttpWebResponse, declared programmatically, but not declared programmatically.
So I created a seperate page without any AJAX objects on it. Since I actually solved the problem on my own time, it becomes my Intellectual Property and not that of my employer (A major Telecomm, whose name I shall not disclose so you don’t mistake my outlook on things as being theirs). Following is the codebase of the code-behind (DownloadFile.aspx.vb):
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.Sql
Imports System.Net
Imports system.io
Partial Class DownloadFile
Inherits System.Web.UI.Page
Protected Sub page_load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
Dim url As String = Request.QueryString(”DownloadUrl”)
If url Is Nothing Or url.Length = 0 Then Exit Sub
‘Initialize the input stream
Dim req As HttpWebRequest = WebRequest.Create(url)
Dim resp As HttpWebResponse = req.GetResponse()
Dim bufferSize As Integer = 1
‘Initialize the output stream
Response.Clear()
Response.AppendHeader(”Content-Disposition:”, “attachment; filename=download.zip”)
Response.AppendHeader(”Content-Length”, resp.ContentLength.ToString)
Response.ContentType = “application/download”
‘Populate the output stream
Dim ByteBuffer As Byte() = New Byte(bufferSize) {}
Dim ms As MemoryStream = New MemoryStream(ByteBuffer, True)
Dim rs As Stream = req.GetResponse.GetResponseStream()
Dim bytes() As Byte = New Byte(bufferSize) {}
While rs.Read(ByteBuffer, 0, ByteBuffer.Length) > 0
Response.BinaryWrite(ms.ToArray())
Response.Flush()
End While
‘Cleanup
Response.End()
ms.Close()
ms.Dispose()
rs.Dispose()
ByteBuffer = Nothing
End Sub
End Class
Yes, I use VB.NET at work, despite being a C# developer. Since I’ve been there for four months, however, my VB.NET fluency is pretty much kicking my C# square in the nuts. Once in a while, when working on a given project, I still find myself initializing objects in the wrong language.
Now, here’s the really sneaky part: How do I transfer the User to this DownloadFile.aspx if I can’t use a Response.Redirect or a Server.Transfer, and I don’t want to make them click an insecure link? The solution hit me like a ton of nostalgic bricks: An IFRAME.
Obviously, I can’t just drop the IFRAME in the main codebase, as I want to change its contents programmatically and I don’t want it to appear until the User clicks the Download button. So I put an ASP:Label on the page whose job is to act as a stealthy IFRAME Generator.
The code that generates the download link is so simple, it makes me laugh and cry at the same time:
lblStealthyFrame.Text = “”
Anyway, I hope anyone out there looking for a way to solve this problem finds this useful. I like it because:
1. This solution doesn’t care if my website is AJAX-Enabled
2. This solution doesn’t care if my User had to click a link
3. This solution doesn’t care if my file is on another server in a far away land.
4. This solution doesn’t require a 3rd-party API, SDK, or Toolkit.
5. While I used Google to determine the exact way to manage the code of the HttpWebRequest, HttpWebResponse, and Memory Stream, it was my own ingenuity that made the solution work.
6. While QueryStrings are usually insecure, the User will never actually see the Page itself render. After all, we’re clearing and closing the Response before the first postback can ever occur.
7. Even if the user goes into “View Source”, they won’t see the IFRAME source. It populated after the Response was rendered as triggered by an AJAX object. Thus it would require some sort of fancy DOM inspector to find the technical methods of accessing the download file (Which I changed in my examples).
If you decide to use it, keep this in mind: The buffer size must be exactly one byte, or your download will lose bits. For example, when I set it to 16384 and pointed it at Google’s banner image, I only got half the picture. When I changed it to one byte, the whole thing finished. My dev server’s CPU and Memory Utilization didn’t jump at all.
So if you need your ASP.NET 2.0 Web Application to download a potentially very-large, binary file and send it back to the user; all without saving a local copy of the very large file, this may be the solution for you. May your Google-Fu be stronger than mine was.

2 Comments
nazari
July 11th, 2008
at 9:41pm
How do I solve my problem when I run this code for DownloadFile I see this error: The remote name could not be resolved: ‘img444.imageshack.us’ .
tsilb
July 11th, 2008
at 10:04pm
Sounds like you’re getting an HTTP 404. Put your target URL into your address bar and see if it exists. Also consider many image hosting sites tend to block non-embedded image requests.