In this blog post I am going to show how to use Background Transfer feature to download files over HTTP in a Windows Store C#/XAML app. Background Transfer has several advantages over using HttpClient and is much better for long running transfers. I am going to create a simple app, that initiates download over the Internet, tracks progress of the download and supports re-attaching transfers after the app is closed.
HttpClient vs Background Transfer
C# developers writing Windows Store apps have two main options to download files from network locations over HTTP:
- HttpClient class (and its GetAsync method)
- Background Transfer functionality
Using HttpClient is only viable when dealing with relatively small files (MSDN suggests 'a couple of KB'). By default HttpClient response content buffer size is set to 64KB and if response is bigger than we would get an error. Buffer size can be increased if necessary (MaxResponseContentBufferSize property), but if you need to do that it is likely that you should use Background Transfer feature instead.
Background Transfer has several other advantages. First of all it runs outside of calling application which means that if the app is suspended, the download can still continue in the background. It also provides inherent support for pause, resume operations and can cope with sudden network status changes automatically. When app terminates, existing downloads will paused and persisted. Moreover it also plays nicely with power management (OS can disable downloads when it deems it necessary) and metered networks (via BackgroundDownloader.CostPolicy property) - after all user may not want to download 1GB+ file over roamed data connection. These two features are very important in mobile scenarios and make Background Transfer even more appealing.
Starting a download
Let's create a simple C#/XAML app that will download a big file from a remote location and store it in the local app data store. Start off with a blank Windows Store app template and modify MainPage.xaml to look like this.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Button Content="Download" Margin="0,150,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" Height="58" Width="145" FontSize="17" Grid.Row="2" Click="DownloadClick"/> <ProgressBar HorizontalAlignment="Center" Height="30" Margin="0,-30,0,0" VerticalAlignment="Center" Width="400" x:Name="DownloadProgress"/> </Grid>
The interface is very simple and consists of a button to initiate download as well as a progress bar to indicate progress.
Here is DownloadClick event handler implementation.
private async void DownloadClick(object sender, RoutedEventArgs e) { const string fileLocation = "http://download.thinkbroadband.com/100MB.zip"; var uri = new Uri(fileLocation); var downloader = new BackgroundDownloader(); StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("100MB.zip", CreationCollisionOption.ReplaceExisting); DownloadOperation download = downloader.CreateDownload(uri, file); await StartDownloadAsync(download); } private void ProgressCallback(DownloadOperation obj) { double progress = ((double) obj.Progress.BytesReceived / obj.Progress.TotalBytesToReceive); DownloadProgress.Value = progress * 100; if(progress >= 1.0) { _activeDownload = null; DownloadButton.IsEnabled = true; } } private async Task StartDownloadAsync(DownloadOperation downloadOperation) { DownloadButton.IsEnabled = false; _activeDownload = downloadOperation; var progress = new Progress<DownloadOperation>(ProgressCallback); await downloadOperation.StartAsync().AsTask(progress); }
First of all we need to obtain StorageFile (or more precisely IStorageFile implementation) - in our scenario we use ApplicationData.Current.LocalFolder.CreateFileAsync() to create a file in local app data store. Please note that we use async/await pattern, hence the event handling method is marked as async. We also need to create BackgroundDownloader class instance that is used to actually create new download using CreateDownload() method.
Every download created using Background Tranfser feature is encapsulated in DownloadOperation object. These objects provide basic operations used to manipulate the download. In example above we start the download using StartAsync() that returns IAsyncOperationWithProgress. We make use of AsTask() extension method to cast the returned value to Task and provide progress callback used to update ProgressBar control.
Handling existing downloads
Previous example is a very simple scenario. Once we terminate the app, we lose track of our download, even though its being paused and persisted by Background Transfer automatically. To address this problem we can use BackgroundDownloader.GetCurrentDownloadsAsync() method to retrieve all active DownloadOperation objects for our applciation. Once we do this we can easily re-attach progress handler and any logic handling completed downloads.
async void MainPageLoaded(object sender, RoutedEventArgs e) { await LoadActiveDownloadsAsync(); } private async Task LoadActiveDownloadsAsync() { IReadOnlyList<DownloadOperation> downloads = null; downloads = await BackgroundDownloader.GetCurrentDownloadsAsync(); if(downloads.Count > 0) { //for simplicity we support only one download await ResumeDownloadAsync(downloads.First()); } }
private async Task ResumeDownloadAsync(DownloadOperation downloadOperation) { DownloadButton.IsEnabled = false; _activeDownload = downloadOperation; var progress = new Progress<DownloadOperation>(ProgressCallback); await downloadOperation.AttachAsync().AsTask(progress); }
Once we have this code in place our download will reattach every time we load the page. For simplicity we support only one active download.
You can find the source on bickbucket .