Browse DevX
Sign up for e-mail newsletters from DevX


Tracking and Resuming Large File Downloads in ASP.NET : Page 4

It's notoriously difficult to deal with large file downloads in Web applications, so for most sites, woe betide users if their download gets interrupted. But it doesn't have to be that way; you can make your ASP.NET applications capable of serving resumable large-file downloads. While you're at it, you can track the download progress so you can handle dynamically created files—and you can get all this without old-fashioned ISAPI DLLs and without unmanaged C++ code.

The FileInformation Helper Class
As you saw in the ZIPHandler section, FileInformation is a helper class which encapsulates the download state, e.g. in-progress, broken, etc. (see Listing 2 for the complete code).

To create an instance of FileInformation, you pass the class constructor the path to the requested file.

   Public Sub New(ByVal sPath As String)
      m_objFile = New System.IO.FileInfo(sPath)
   End Sub
FileInformation uses a System.IO.FileInfo object to get information about that file, which it exposes as properties, for example, whether the file exists, its full name, size, etc. The class also exposes a DownloadState enumeration that describes the various states of a download request:

     <Flags()> Enum DownloadState
       ' Clear: No download in progress, 
       ' the file can be manipulated
       fsClear = 1
       ' Locked: A dynamically created file must
       ' not be changed
       fsLocked = 2
       ' In Progress: File is locked, and download 
       ' is currently in progress
       fsDownloadInProgress = 6
       ' Broken: File is locked, download was in
       ' progress, but was cancelled 
       fsDownloadBroken = 10
       ' Finished: File is locked, download
       ' was completed
       fsDownloadFinished = 18
     End Enum
FileInformation also provides the EntityTag property value. The sample code has a hard-coded value in it, because the sample uses only one download file, which will not be changed, but for a real-world application, where you're serving multiple files, or even create files dynamically, your code must provide unique EntityTag values for each file. Plus, each time that you change or edit the file that value must change as well. This enables client software to verify if the chunk they downloaded before is still up-to-date. Here's the section that returns the hard-coded EntityTag value in the sample code.

   Public ReadOnly Property EntityTag() As String
       ' The EntityTag used in the initial (200) response 
       ' to, and in resume-Requests from clients 
         ' ToDo - your code here
         ' (Create a unique string for your file)
         ' Please note, that this unique code must remain
         ' the same as long as the file does not change. 
         ' If the file DOES change or is edited, however,
         ' the code MUST change.
         Return "MyExampleFileID"
       End Get
     End Property
A simple and probably safe enough EntityTag could be a combination of the file name and the file's last modified date. Whatever method you choose, please make sure that it is truly unique and can't be confused with another file's EntityTag. I prefer to name dynamically created files in my applications after the client, customer, and zip queue indexes, and use a GUID saved in a database for the EntityTag.

The ZipFileHandler reads and sets the public State property. After a completed download, it sets State to fsDownloadFinished. At that point you can delete temporary files. Always call the Save method here, to persist the state.

   Public Property State() As DownloadState
         Return m_nState
      End Get
      Set(ByVal nState As DownloadState)
         m_nState = nState
         ' ToDo - optional
         ' At this point, you could delete the 
         ' file automatically. 
         ' If the state is set to Finished, you
         '  might not need the file anymore:
         ' If nState = _
         '   DownloadState.fsDownloadFinished Then
         '   Clear()
         ' Else
         '   Save()
         ' End If
      End Set
   End Property
The ZipFileHandler should call the Save method whenever the file state changes, saving the file's state, so it can be displayed to the user at a later time. You can also use it to save the EntityTag you created. Do not save the file's state and EntityTag value to the Application, Session, or Cache—you must persist that information across any of those lifecycles.

   Private Sub Save()
      ' ToDo - your code here 
      ' Save the state of this file's download 
      ' to a database or XML file...)
      ' If you do not create files dynamically, 
      ' you do not need to save the state, of course.
   End Sub
As written, the sample code handles only one existing file (download.zip); however you can enhance it to create requested files on demand.

When testing the sample code, your local system or LAN will probably be too fast to interrupt the download, so I recommend you use either a slow LAN connection (one way to simulate one is to reduce your site's bandwidth in IIS) or a live server on the Internet.

Downloading can still be tough on the client side. Broken or misconfigured Web cache servers operated by ISPs can ruin large downloads, inducing corruption or early session termination. If your files exceed 255 MB in size, you might want to encourage your customers to use third-party download manager software, although some newer browsers have basic download managers built-in.

If you want to extend the sample code even more, it's worth taking another look at the HTTP specifications. You could create MD5 digest values for the download, and add them using Content-MD5 header, providing a way to check the integrity of the download. The sample doesn't cover HTTP methods other than GET and HEAD, either.

Alexander Schaaf has been a freelance developer and consultant for more than nine years, specializing in Microsoft technologies. He is co-founder and chief software developer of, alexander-schaaf.com and works on projects all over Europe. Alexander lives with his family in Germany.
Thanks for your registration, follow us on our social networks to keep up-to-date