Home  |  Created on - November 2005


Resumable File Downloads with ASP.Net

Has this ever happened to you (I bet the answer is "Yes!")? You are downloading a really large file. When it is almost complete, there is a glitch of some sort, and the download aborts. Then you have to go back and start from the beginning — annoying enough on a high-speed Internet connection and really maddening for the many people who still use a modem and phone line.

But sometimes an interrupted download does not resume at the beginning; rather, it picks up where it left off, turning a major hassle into a minor inconvenience. This is a great feature, one you should implement on your Web site if you make large files available for download.

The most common way to provide a file for download is to put it on your Web site and insert a standard hyperlink to the file on one of your pages. When the user clicks a link to a file whose type is not recognized by the browser, such as a ZIP file, they are given the option of downloading it. Simple and effective, right? Well, simple to be sure, but resumability is not supported with this technique.

ASP.Net developers have gotten more sophisticated with file downloads, abandoning the default link as described above for use of the Response.WriteFile() and Response.End() methods. This permits you to create an ASPX page for downloads and provides the ability to require a login and also to generate the file on the fly, so that all your files for download do not clog up your server. But WriteFile() can be a memory hog to boot, reading the entire file into memory before sending it to the client. This was addressed with a hotfix, part of .Net Framework 1.1 Service Pack. And still, there is no resumability.

Here is the solution

The solution lies in two seemingly unrelated features. I learned about the first in a KnowledgeBase article that dealt with the memory hogging problems of the original WriteFile(). The solution involved performing the download in sections rather than all at once. The file to be downloaded is broken into more manageable chunks; before each is sent, the server uses the Response.IsClientConnected property to verify that the client is still connected and available to receive more data. Only then is the next chunk sent.

From the perspective of the server, this provides two advantages when and if the connection is broken:

  • The server does not continue sending data to a disconnected client.
  • If the download file was generated dynamically it can be kept rather than deleted on the assumption that the user will try the download again.

    But implementing resumable downloads required another piece of the puzzle. This comes from our old friend HTML.

    I was quite surprised that the HTML specification supports headers designed specifically for implementing resumable downloads. These headers use the concept of ranges to permit a download to resume where it left off.

Two header tags are critical:

  • The Accept-Ranges element tells the client that it supports resuming downloads.
  • The ETag (entity tag) element provides a unique ID identifying the session.

For example, the headers returned by IIS to a client at the start of a resumable download might look like this:

HTTP/1.1 200 OK
Connection: close
Date: Wed, 7 Sep 2005 21:10:19 GMT
Accept-Ranges: bytes
Last-Modified: Sun, 4 Sep 2005 05:34:51 GMT
ETag: "21accd5ffa11d90:3099"
Cache-Control: private
Content-Type: application/x-zip-compressed
Content-Length: 6511789
				

Having received these headers, Internet Explorer knows that the server supports resuming downloads. Then, if the download is interrupted, IE sends the ETag value and a range value (which indicates how much has been downloaded so far) back to the server to try to resume the download. Here's an example of some of the headers that would be sent in this situation:

GET http://204.181.112.112/data.zip HTTP/1.0
Range: bytes=1254660-
Unless-Modified-Since: Wed, 7 Sep 2005 21:10:19 GMT
If-Range: "21accd5ffa11d90:3099"
				

You can see that these headers include the URL of the file that was being downloaded as well as the date and time the original download was commenced. The server uses this value to determine if the file has changed and, if so, begins a new download. If the file has not changed, IIS resumes the download with a response that includes headers such as these:

HTTP/1.1 206 Partial Content
Content-Range: bytes 1254660-6511788/6511789
Accept-Ranges: bytes
Last-Modified:  Wed, 7 Sep 2005 21:10:19 GMT
ETag:  "21accd5ffa11d90:3099"  
Cache-Control: private
Content-Type: application/x-zip-compressed
Content-Length: 5257329
				

Note that the Content-Length value now specifies the remaining bytes to be downloaded. In other words, the total file size minus the Range value that IE sent to the server when the download was interrupted.

Next>>