
Downloading files programmatically is a common task that most programming languages expose different APIs for.
I believe it is useful to have examples to refer to for how to accomplish this in your language of choice, both synchronously and asynchronously.
This article covers how to download files with C# using the classes and methods that are conveniently built into the .NET Framework.
The options
When using C# there are two main options that .NET provides us with.
These are the WebClient
and HttpClient
classes which I will cover in the following sections.
Note that you can also work with the HttpRequestMessage
class directly for low-level access to HTTP requests, but I find that this approach is rarely required, especially not for simpler operations like downloading files.
WebClient
Since the very first versions of the .NET Framework, the WebClient
class has provided some really useful methods for working with files.
This includes methods to download files, strings, and arbitrary binary data using byte arrays. The WebClient
class also includes methods for uploading resources to web servers.
Let’s look at a few examples of how to download a file using WebClient
.
Synchronous example
First of all, make sure you have the appropriate using
statement in place, as follows.
using System.Net;
This is needed in order to use the WebClient
class without requiring a fully qualified namespace.
The WebClient
class itself is really easy to use. The very simplest example of downloading a file is as follows.
using (var client = new WebClient()) { client.DownloadFile("https://via.placeholder.com/300.png", "300.png"); }
The DownloadFile
method accepts a URL to download a file from and a local file path to download the file to.
WebClient
implements the IDisposable
interface, so it is best practice to wrap the code in a using
block to make sure resources are properly disposed of after we’ve finished.
All of the above happens synchronously i.e. the code waits until the file download has completed before continuing program execution past the DownloadFile
method call.
Asynchronous example
What if we want to download a file asynchronously and report progress?
Unfortunately, the DownloadFile
method does not provide a way of reporting progress, as it is a synchronous method.
However, there is a built-in way of asynchronously downloading a file and reporting progress with the WebClient
class, as demonstrated in the example below.
using (var client = new WebClient()) { client.DownloadFileCompleted += (s, e) => Console.WriteLine("Download file completed."); client.DownloadProgressChanged += (s, e) => Console.WriteLine($"Downloading {e.ProgressPercentage}%"); client.DownloadFileAsync(new Uri("https://via.placeholder.com/300.png"), "300.png"); }
The above code is very similar to the synchronous example. However, we’re now calling the DownloadFileAsync
method which expects a Uri
(Uniform Resource Identifier) as the first parameter.
Additionally, two event handlers are wired up before the file download commences.
The DownloadProgressChanged
event is fired periodically as the download progresses and provides access to some useful properties such as BytesReceived
and ProgressPercentage
.
As expected, the DownloadFileCompleted
event is fired whenever the file download has completed.
This is all “well and good”, but what if we want to get progress updates and wait for the file download to complete before allowing the code to continue to the next statement?
As it stands, the above code will continue past the DownloadFileAsync
method call while the download is in progress, since it is an asynchronous method call.
Asynchronous wait example
In order to download the file asynchronously using the DownloadFileAsync
method and wait until the download has completed before continuing program execution, we need to dip into the world of reset events.
The example below demonstrates how to accomplish this.
using (var client = new WebClient()) using (var completedSignal = new AutoResetEvent(false)) { client.DownloadFileCompleted += (s, e) => { Console.WriteLine("Download file completed."); completedSignal.Set(); }; client.DownloadProgressChanged += (s, e) => Console.WriteLine($"Downloading {e.ProgressPercentage}%"); client.DownloadFileAsync(new Uri("https://via.placeholder.com/300.png"), "300.png"); completedSignal.WaitOne(); }
The AutoResetEvent
in the above code allows us to wait at the point where the WaitOne
method is called.
When the DownloadFileCompleted
event is fired, the Set
method on the AutoResetEvent
sends a signal that allows the code to proceed to the next statement.
Note that the use of AutoResetEvent
as shown above, is a neat little trick that can be applied to any other asynchronous methods you want to call and subsequently wait for completion.
Async await example
Although the above example works, there is a cleaner way to achieve the same result, providing you are targeting .NET Framework 4.5 or greater, or .NET Core 2.0 (.NET Standard 2.0) or greater.
The example below demonstrates this approach.
using (var client = new WebClient()) { client.DownloadFileCompleted += (s, e) => Console.WriteLine("Download file completed."); client.DownloadProgressChanged += (s, e) => Console.WriteLine($"Downloading {e.ProgressPercentage}%"); await client.DownloadFileTaskAsync("https://via.placeholder.com/300.png", "300.png"); }
By using the await
keyword when calling the DownloadFileTaskAsync
method, the code will wait until the file download has completed, while at the same time carrying out the download asynchronously and firing the progress events.
This is a much nicer approach if you’re able to use it as a result of targeting a new enough version of .NET and you’re in an async
context.
Note that whatever method you use the above code within needs to be marked with the async
keyword.
HttpClient
Since .NET Framework 4.5, the HttpClient
class has quickly taken over as the recommended means of working with any kind of HTTP request, including file downloads.
HttpClient
offers lots of different methods and is very powerful.
Before attempting to use the HttpClient
class, make sure you have the appropriate using
statement in place, as follows.
using System.Net.Http;
Here’s the simplest possible example of downloading a file using HttpClient
.
var httpClient = new HttpClient(); using (var stream = await httpClient.GetStreamAsync("https://via.placeholder.com/300.png")) { using (var fileStream = new FileStream("300.png", FileMode.CreateNew)) { await stream.CopyToAsync(fileStream); } }
Note that even though HttpClient
implements IDisposable
it is recommended that you create one instance of HttpClient
and reuse this throughout your program to avoid problems such as socket exhaustion. I’ve simply newed up a HttpClient
instance inline in the above example for demonstration purposes.
As you can see from the above example, the code required to download a file using HttpClient
is a little lower level compared to using WebClient
, since we need to work with streams directly. Nonetheless, the code is still straightforward to follow along with.
Note that as per the previous example, whatever method you use the above code within needs to be marked with the async
keyword.
For any new development work, it is recommended that you use HttpClient
. It has a number of advantages over WebClient
, including more configuration options and it facilitates easier mocking and testing.
However, it does have some disadvantages, such as the lack of built-in progress reporting. If this is a requirement and you want to use HttpClient
you’ll need to roll your own solution for this.
Summary
In this article, I have covered the two main ways of downloading files using C# and the .NET Framework.
WebClient
makes it really easy to download files, with its high-level API and it is available regardless of what .NET version you are targeting.
Although it is technically ‘obsolete’ WebClient
can be a good choice for simple scenarios.
HttpClient
is now the recommended way to work with HTTP requests, including file downloads, from .NET Framework 4.5 onwards.
Use HttpClient
whenever you need more control and as the recommended option for new development.
Comments