Downloading Files with BackgroundDownloader in Windows Store Apps – Boredom Challenge Day 6

Standard

In one of my previous articles, I’ve mentioned the Background Transfer API for Windows 8, which allowed us to use BackgroundDownloader and BackgroundUploader classes for downloading and uploading files in our app. After mentioning it, I’ve decided to write an article about how we can use them.

1

Background Transfer API has many nice features that make our work easier, such as:

– Being able to download large files,
– Tracking download progress,
– Pausing / resuming download,
– Continuing download even when our app is in the background (suspended),
– If the app is closed mid-download, allowing us to continue the next time our app starts,
– Setting cost policy (wheter downloading when connected to metered connections or not),
– Handling network situations automatically (no need to catch exceptions if the network connection is lost mid-download, it’ll continue when internet comes back),
– Multiple downloads at the same time.

As always, we will create an example application that uses these features.

Before starting our app, I’ve created a ~97 MB image file (with .bmp extension) in my Dropbox (also a small, 5 MB zipped version of it) to use in our app. Their links are:
https://dl.dropboxusercontent.com/u/85324883/bigimage.bmp
https://dl.dropboxusercontent.com/u/85324883/bigimage.zip

Ok then, we create a new blank Windows Store App in Visual Studio.

2

Next, we create a simple interface: A “Start Download” button, a ProgressBar to show the download progress, a button to pause/resume, a button to cancel download and related textblock which will show the status and other information:

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Button x:Name="ButtonDownload" Content="Start Download" HorizontalAlignment="Left" Margin="100,100,0,0" VerticalAlignment="Top" Click="ButtonDownload_Click"/>
        <Button x:Name="ButtonPauseResume" Content="Pause" HorizontalAlignment="Left" Margin="100,204,0,0" VerticalAlignment="Top" IsEnabled="False" Width="87" Click="ButtonPauseResume_Click"/>
        <Button x:Name="ButtonCancel" Content="Cancel" HorizontalAlignment="Left" Margin="192,204,0,0" VerticalAlignment="Top" IsEnabled="False" Width="87" Click="ButtonCancel_Click"/>
        <ProgressBar x:Name="ProgressBarDownload" HorizontalAlignment="Left" Height="10" Margin="100,189,0,0" VerticalAlignment="Top" Width="390"/>
        <TextBlock x:Name="TextBlockProgress" HorizontalAlignment="Left" Margin="100,166,0,0" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="15"/>
        <TextBlock x:Name="TextBlockStatus" HorizontalAlignment="Left" Margin="100,143,0,0" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="15"/>
    </Grid>

Now, we will add the necessary code in MainPage.xaml.cs. I’ll paste all of the code below, and then we will divide and inspect it:

using Windows.Networking.BackgroundTransfer;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
using Windows.Storage;
        DownloadOperation downloadOperation;
        CancellationTokenSource cancellationToken;

        BackgroundDownloader backgroundDownloader = new BackgroundDownloader();
        public MainPage()
        {
            this.InitializeComponent();
            this.Loaded += MainPage_Loaded;
        }

        async void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            IReadOnlyList<DownloadOperation> downloads = await BackgroundDownloader.GetCurrentDownloadsAsync();
            if (downloads.Count > 0)
            {
                downloadOperation = downloads.First();
                cancellationToken = new CancellationTokenSource();
                Progress<DownloadOperation> progress = new Progress<DownloadOperation>(progressChanged);
                ButtonDownload.IsEnabled = false;
                ButtonCancel.IsEnabled = true;
                ButtonPauseResume.IsEnabled = true;
                try
                {
                    TextBlockStatus.Text = "Initializing...";
                    await downloadOperation.AttachAsync().AsTask(cancellationToken.Token, progress);
                }
                catch (TaskCanceledException)
                {
                    TextBlockStatus.Text = "Download canceled.";
                    downloadOperation.ResultFile.DeleteAsync();
                    ButtonPauseResume.Content = "Resume";
                    ButtonCancel.IsEnabled = false;
                    ButtonCancel.IsEnabled = false;
                    ButtonPauseResume.IsEnabled = false;
                    ButtonDownload.IsEnabled = true;
                    downloadOperation = null;
                }
            }
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        async private void ButtonDownload_Click(object sender, RoutedEventArgs e)
        {
            FolderPicker folderPicker = new FolderPicker();
            folderPicker.SuggestedStartLocation = PickerLocationId.Downloads;
            folderPicker.ViewMode = PickerViewMode.Thumbnail;
            folderPicker.FileTypeFilter.Add("*");
            StorageFolder folder = await folderPicker.PickSingleFolderAsync();
            if (folder != null)
            {
                StorageFile file = await folder.CreateFileAsync("bigpicture.bmp", CreationCollisionOption.GenerateUniqueName);
                downloadOperation = backgroundDownloader.CreateDownload(new Uri("https://dl.dropboxusercontent.com/u/85324883/bigimage.bmp"), file);
                Progress<DownloadOperation> progress = new Progress<DownloadOperation>(progressChanged);
                cancellationToken = new CancellationTokenSource();
                ButtonDownload.IsEnabled = false;
                ButtonCancel.IsEnabled = true;
                ButtonPauseResume.IsEnabled = true;
                try
                {
                    TextBlockStatus.Text = "Initializing...";
                    await downloadOperation.StartAsync().AsTask(cancellationToken.Token, progress);
                }
                catch (TaskCanceledException)
                {
                    TextBlockStatus.Text = "Download canceled.";
                    downloadOperation.ResultFile.DeleteAsync();
                    ButtonPauseResume.Content = "Resume";
                    ButtonCancel.IsEnabled = false;
                    ButtonCancel.IsEnabled = false;
                    ButtonPauseResume.IsEnabled = false;
                    ButtonDownload.IsEnabled = true;
                    downloadOperation = null;
                }
            }
        }

        private void progressChanged(DownloadOperation downloadOperation)
        {
            int progress = (int)(100 * ((double)downloadOperation.Progress.BytesReceived / (double)downloadOperation.Progress.TotalBytesToReceive));
            TextBlockProgress.Text = String.Format("{0} of {1} kb. downloaded - %{2} complete.", downloadOperation.Progress.BytesReceived / 1024, downloadOperation.Progress.TotalBytesToReceive / 1024, progress);
            ProgressBarDownload.Value = progress;
            switch (downloadOperation.Progress.Status)
            {
                case BackgroundTransferStatus.Running:
                    {
                        TextBlockStatus.Text = "Downloading...";
                        ButtonPauseResume.Content = "Pause";
                        break;
                    }
                case BackgroundTransferStatus.PausedByApplication:
                    {
                        TextBlockStatus.Text = "Download paused.";
                        ButtonPauseResume.Content = "Resume";
                        break;
                    }
                case BackgroundTransferStatus.PausedCostedNetwork:
                    {
                        TextBlockStatus.Text = "Download paused because of metered connection.";
                        ButtonPauseResume.Content = "Resume";
                        break;
                    }
                case BackgroundTransferStatus.PausedNoNetwork:
                    {
                        TextBlockStatus.Text = "No network detected. Please check your internet connection.";
                        break;
                    }
                case BackgroundTransferStatus.Error:
                    {
                        TextBlockStatus.Text = "An error occured while downloading.";
                        break;
                    }
            }
            if (progress >= 100)
            {
                TextBlockStatus.Text = "Download complete.";
                ButtonCancel.IsEnabled = false;
                ButtonPauseResume.IsEnabled = false;
                ButtonDownload.IsEnabled = true;
                downloadOperation = null;
            }
        }

        private void ButtonPauseResume_Click(object sender, RoutedEventArgs e)
        {
            if (ButtonPauseResume.Content.ToString() == "Pause")
            {
                try
                {
                    downloadOperation.Pause();
                }
                catch (InvalidOperationException)
                {

                }
            }
            else
            {
                try
                {
                    downloadOperation.Resume();
                }
                catch (InvalidOperationException)
                {
                }
            }
        }

        private void ButtonCancel_Click(object sender, RoutedEventArgs e)
        {
            cancellationToken.Cancel();
            cancellationToken.Dispose();
        }

We’ll analyze what we’re doing, starting with the Click event of ButtonDownload:

        async private void ButtonDownload_Click(object sender, RoutedEventArgs e)
        {
            FolderPicker folderPicker = new FolderPicker();
            folderPicker.SuggestedStartLocation = PickerLocationId.Downloads;
            folderPicker.ViewMode = PickerViewMode.Thumbnail;
            folderPicker.FileTypeFilter.Add("*");
            StorageFolder folder = await folderPicker.PickSingleFolderAsync();
            if (folder != null)
            {
                StorageFile file = await folder.CreateFileAsync("bigpicture.bmp", CreationCollisionOption.GenerateUniqueName);
                downloadOperation = backgroundDownloader.CreateDownload(new Uri("https://dl.dropboxusercontent.com/u/85324883/bigimage.bmp"), file);
                Progress<DownloadOperation> progress = new Progress<DownloadOperation>(progressChanged);
                cancellationToken = new CancellationTokenSource();
                ButtonDownload.IsEnabled = false;
                ButtonCancel.IsEnabled = true;
                ButtonPauseResume.IsEnabled = true;
                try
                {
                    TextBlockStatus.Text = "Initializing...";
                    await downloadOperation.StartAsync().AsTask(cancellationToken.Token, progress);
                }
                catch (TaskCanceledException)
                {
                    TextBlockStatus.Text = "Download canceled.";
                    downloadOperation.ResultFile.DeleteAsync();
                    ButtonPauseResume.Content = "Resume";
                    ButtonCancel.IsEnabled = false;
                    ButtonCancel.IsEnabled = false;
                    ButtonPauseResume.IsEnabled = false;
                    ButtonDownload.IsEnabled = true;
                    downloadOperation = null;
                }
            }
        }

Here, we begin by making the user select a folder to download the file to. Then, using BackgroundDownloader, we create a DownloadOperation by specifying the download link and the StorageFile object that will be used for the operation. Next, we set a callback for the progress of the download, where we will update the UI and see the status of the download. Finally, we create a cancellation token that will be used for cancelling the task that contains the download operation if the user wants. After everything is complete, we start the download operation.

Note that since a TaskCanceledException is thrown when a task is canceled, we have to catch and handle the cancel function here where we need to delete the remains of the downloaded file (otherwise it will leave a 0 kb file in the selected folder).

Let’s look at the code in our progressChanged function:

        private void progressChanged(DownloadOperation downloadOperation)
        {
            int progress = (int)(100 * ((double)downloadOperation.Progress.BytesReceived / (double)downloadOperation.Progress.TotalBytesToReceive));
            TextBlockProgress.Text = String.Format("{0} of {1} kb. downloaded - %{2} complete.", downloadOperation.Progress.BytesReceived / 1024, downloadOperation.Progress.TotalBytesToReceive / 1024, progress);
            ProgressBarDownload.Value = progress;
            switch (downloadOperation.Progress.Status)
            {
                case BackgroundTransferStatus.Running:
                    {
                        TextBlockStatus.Text = "Downloading...";
                        ButtonPauseResume.Content = "Pause";
                        break;
                    }
                case BackgroundTransferStatus.PausedByApplication:
                    {
                        TextBlockStatus.Text = "Download paused.";
                        ButtonPauseResume.Content = "Resume";
                        break;
                    }
                case BackgroundTransferStatus.PausedCostedNetwork:
                    {
                        TextBlockStatus.Text = "Download paused because of metered connection.";
                        break;
                    }
                case BackgroundTransferStatus.PausedNoNetwork:
                    {
                        TextBlockStatus.Text = "No network detected. Please check your internet connection.";
                        break;
                    }
                case BackgroundTransferStatus.Error:
                    {
                        TextBlockStatus.Text = "An error occured while downloading.";
                        break;
                    }
            }
            if (progress >= 100)
            {
                TextBlockStatus.Text = "Download complete.";
                ButtonCancel.IsEnabled = false;
                ButtonPauseResume.IsEnabled = false;
                ButtonDownload.IsEnabled = true;
                downloadOperation = null;
            }
        }

In this function, we begin by calculating the percentage of the download, and update the UI with it. Then, using a switch – case statement, we show the status of the download with appropriate messages, and finally check if the download is complete.

Let’s look at the pause-resume and cancel buttons:

        private void ButtonPauseResume_Click(object sender, RoutedEventArgs e)
        {
            if (ButtonPauseResume.Content.ToString() == "Pause")
            {
                try
                {
                    downloadOperation.Pause();
                }
                catch (InvalidOperationException)
                {

                }
            }
            else
            {
                try
                {
                    downloadOperation.Resume();
                }
                catch (InvalidOperationException)
                {
                }
            }
        }

        private void ButtonCancel_Click(object sender, RoutedEventArgs e)
        {
            cancellationToken.Cancel();
            cancellationToken.Dispose();
        }

This is pretty straightforward. The only thing that needs explaining is that, we catch InvalidOperationExceptions because they may be thrown if we try to resume a download that is already running (or pause a download that’s already paused). This can happen if the user repeatedly smashes (especially) the resume button, since it may take a while for the download to start again (This can be avoided but since this is only an example, we haven’t bothered with a perfect UI). The cancel button is also simple, we just call the Cancel method and handle it in the aforementioned TaskCanceledException.

Finally, let’s take a look at the MainPage.Loaded part:

        async void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            IReadOnlyList<DownloadOperation> downloads = await BackgroundDownloader.GetCurrentDownloadsAsync();
            if (downloads.Count > 0)
            {
                downloadOperation = downloads.First();
                cancellationToken = new CancellationTokenSource();
                Progress<DownloadOperation> progress = new Progress<DownloadOperation>(progressChanged);
                ButtonDownload.IsEnabled = false;
                ButtonCancel.IsEnabled = true;
                ButtonPauseResume.IsEnabled = true;
                try
                {
                    TextBlockStatus.Text = "Initializing...";
                    await downloadOperation.AttachAsync().AsTask(cancellationToken.Token, progress);
                }
                catch (TaskCanceledException)
                {
                    TextBlockStatus.Text = "Download canceled.";
                    downloadOperation.ResultFile.DeleteAsync();
                    ButtonPauseResume.Content = "Resume";
                    ButtonCancel.IsEnabled = false;
                    ButtonCancel.IsEnabled = false;
                    ButtonPauseResume.IsEnabled = false;
                    ButtonDownload.IsEnabled = true;
                    downloadOperation = null;
                }
            }
        }

Here, when our application opened, we check the current downloads – which mean downloads that weren’t completed or canceled. Therefore, we determine if a download was in progress before our app was closed, and if there was such a download, we take it and start it again by attaching our progressChanged function and a cancellation token. For simplicity, our code only allows one download at a time, but if anyone wants (you can leave a comment below) or if I deem it worth a blog post I can make an example with multiple downloads at the same time.

That’s it, let’s run our application to see it in action. Try pausing – resuming, canceling, closing the app and opening it again, or even disconnecting from your network during download.

3

4

5

Here‘s the link to our example’s source code.

Thank you for reading.

9 thoughts on “Downloading Files with BackgroundDownloader in Windows Store Apps – Boredom Challenge Day 6

    • Hi,

      Can you explain your issue a little bit more? Do you get an exception, does the download stop, or does it just stay at 0 byte? Have you tried downloading other files or from another internet connection?

  1. i am getting an exception at (await downloadOperation.StartAsync().AsTask(cancellationToken.Token, progress);) when i am using http link. can you please tell me will it works only for https

  2. Avanesh Sharma

    Hay Thanks for this blog, It can be very smart if you post multiple downloads at a time using BackgroundDownloader.
    I want the code of multiple downloading please post as soon as possible

    Thanks

Comment