Introduction
For one of my projects at work, I needed the ability to download a file from a given URL. Unfortunately, the methods provided by the WebClient
class don't do anything more than download the data. I needed something that gave the ability to provide progress feedback and that was resumable.
As I started my searches on the web for any existing code, I came across two articles on CodeProject that helped get me very close to what I needed. The first article was by John Batte, while the second article by Phil Crosby was a derivation of his work. Using both of these articles as a starting point, I was able to make another derivation that accomplished my goals.
The code samples and content of this article are focused on the .NET 2.0 implementation and any code updates have been made only to that version. The .NET 1.1 implementation is provided for backwards compatibility only and I have no further plans to continue making changes to it.
Using the Code
The FileDownloader
class is the main class and is very simple to use. The minimal implementation is:
private void DoDownload()
{
FileDownloader downloader = new FileDownloader();
downloader.Download(new Uri("http://www.codeproject.com" +
"/info/stuff/cp_bg_black1024.gif"), @"C:\Temp");
}
This creates a new instance of the FileDownloader
class and downloads the file located at "http://www.codeproject.com/info/stuff/cp_bg_black1024.gif" to the "C:\Temp" folder.
FileDownloader
actually performs all download operations asynchronously. When the FileDownloader.Download(Uri url, string destinationFolder)
method is called, it accomplishes a synchronous download by waiting until a signal is set indicating that the download has completed. This allows for both synchronous downloads -- through the use of the Download
methods -- or asynchronous downloads, through the use of the DownloadAsync
methods. Both the synchronous and asynchronous methods ultimately call the same internal method to perform the actual download.
This internal method, called DownloadAsyncCore
, instantiates an instance of the DownloadInfo
internal class. This class is similar in nature to the FileInfo
class provided by the .NET CLR and provides FileDownloader
with all of the information required to actually download the file. This includes:
- The length of the file being downloaded, if known
- The
Stream
that is being used to actually download the file - The
WebResponse
that is servicing the request
Once the DownloadInfo
class is instantiated, the data is downloaded in 1KB chunks and written to the disk. This is the default block size and can be changed through the BlockSize
property. It is this implementation that allows the download to resume if it is cancelled or interrupted.
While this implementation is functional, it does prevent anyone from using the class who is behind a proxy or who requires authentication to access the requested URL. In order to support these scenarios, the FileDownloader
class provides both a Proxy
property that accepts an IWebProxy
derived object and a Credentials
property that accepts an ICredentials
derived object. These properties are also passed on to the underlying DownloadInfo
object.
Finally, the events provide progress feedback. The WebClient
class in .NET 2.0 has asynchronous download methods that provide just such events. These methods are serviced by the DownloadCompletedEventArgs
and DownloadProgressChangedEventArgs
EventArgs
derived classes. Unfortunately, both of these classes have internal constructors and are new in .NET 2.0, so I wasn't able to make use of them. Instead, I created a new triplet set of EventArgs
derived classes to handle the three events that are exposed by the FileDownloader
class. These events are:
public event EventHandler<FileDownloadCompletedEventArgs> DownloadCompleted
public event EventHandler<FileDownloadProgressChangedEventArgs> DownloadProgressChanged;
public event EventHandler<FileDownloadStatusChangedEventArgs> DownloadStatusChanged;
These events are raised during the actual file download to provide the caller with status information (DownloadStatusChanged
), progress information (DownloadProgressChanged
) and completion information (DownloadCompleted
). A complete example looks like this:
private void DoDownload()
{
Uri fileUrl = new Uri("http://www.codeproject.com" +
"/info/stuff/cp_bg_black1024.gif");
string tempPath = Path.GetTempPath();
string fileName = Path.GetFileName(fileUrl.AbsoluteUri);
string downloadPath = Path.Combine(tempPath, fileName);
FileDownloader downloader = new FileDownloader();
downloader.Proxy = WebRequest.DefaultWebProxy;
downloader.Credentials = CredentialCache.DefaultCredentials;
downloader.DownloadCompleted += new
EventHandler<FileDownloadCompletedEventArgs>(OnDownloadCompleted);
downloader.DownloadProgressChanged += new
EventHandler<FileDownloadProgressChangedEventArgs>(
OnDownloadProgressChanged);
downloader.DownloadStatusChanged += new
EventHandler<FileDownloadStatusChangedEventArgs>(
OnDownloadStatusChanged);
downloader.Download(fileUrl, tempPath);
}
private void OnDownloadCompleted(object sender,
FileDownloadCompletedEventArgs e)
{
}
private void OnDownloadProgressChanged(object sender,
FileDownloadProgressChangedEventArgs e)
{
}
private void OnDownloadStatusChanged(object sender,
FileDownloadStatusChangedEventArgs e)
{
}
Here is the same example using VB.NET. This example is based on the comment [^] by CBlackburn [^].
Private Sub DoDownload()
Dim fileUrl As Uri = New Uri("http://www.codeproject.com/" & _
"info/stuff/cp_bg_black1024.gif")
Dim tempPath As String = Path.GetTempPath()
Dim fileName As String = Path.GetFileName(fileUrl.AbsoluteUri)
Dim downloadPath As String = Path.Combine(tempPath, fileName)
Dim downloader As FileDownloader = New FileDownloader()
downloader.Proxy = WebRequest.DefaultWebProxy
downloader.Credentials = CredentialCache.DefaultCredentials
AddHandler downloader.DownloadCompleted, _
AddressOf OnDownloadCompleted
AddHandler downloader.DownloadProgressChanged, _
AddressOf OnDownloadProgressChanged
AddHandler downloader.DownloadStatusChanged, _
AddressOf OnDownloadStatusChanged
downloader.Download(fileUrl, tempPath)
End Sub
Private Sub OnDownloadCompleted(ByVal sender As Object, _
ByVal e As FileDownloadCompletedEventArgs)
End Sub
Private Sub OnDownloadProgressChanged(ByVal sender As Object, _
ByVal e As FileDownloadProgressChangedEventArgs)
End Sub
Private Sub OnDownloadStatusChanged(ByVal sender As Object, _
ByVal e As FileDownloadStatusChangedEventArgs)
End Sub
The complete public API for the FileDownloader
class is available in the documentation CHM file that is part of the download.
Points of Interest
While the FileDownloader
class is available as both a .NET 2.0 and .NET 1.1 solution, only the .NET 2.0 solution has been updated to provide the asynchronous download features and other modifications listed in the revision history.
The DownloadInfo
class implements the IDisposable
pattern. However, the way this pattern is implemented may be different than many of you have seen before. For more information, please refer to this article: Implementing IDisposable and the Dispose Pattern Properly [^] .
Revision History
16-August-2007:
- Added the ability to set the User-agent HTTP header.
12-August-2007:
- Added asynchronous support
- Added the ability to retrieve the last modified date of the file
- Added support for Pre-Authenticating the web request
- Added the ability to download an HTML page
- Added the ability to overwrite the file downloaded if it already exists
11-January-2007:
- Added a complete example in VB.NET
27-August-2006:
- Updated source code to include an application configuration file
25-August-2006: