E-Mail:

Programatically Download File from Remote Location to User Through Server

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

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’ .

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.

What Do You Think?

 

Want to Start a Blog Here for Free?

Are you an expert in one subject or another? If your goal is to help others and dispense hard-earned information back to the community, stake a claim on your very own Lockergnome blog today! You can write about anything - no matter the topic. Sign-up to start blogging!

Uncategorized - Dec 31, 2007

What Should Load with Vista?

Uncategorized - Dec 31, 2007

Server Rack Installed in Home Computer Lab

Uncategorized - Dec 8, 2007

Review of the Nerf Maverick from ThinkGeek

58 queries / 2.521 seconds.