How to download a file (XAML)
This topic will show you how to download a file.
Apps can use the APIs discussed in this topic to enable their app to interact with web services to consume or share popular media formats like photos, music, and video.
There are three primary options when developing an app in either C# or C++ that can be used to request files from locations on the Internet. Small files, like site assets that are retrieved frequently, can be downloaded using the HttpClient (C#) and IXMLHTTPRequest2(C++) APIs.
Alternatively, for larger downloads (video and music) with an operational lifetime that may span beyond multiple app suspensions and/or changes in network availability, your app can use Background Transfer. For a high-level look at Background Transfer, see Transferring data in the background.
Prerequisites
For general help creating a C# Windows Runtime app, see Create your first Windows Runtime app using C# or Visual Basic. For general help creating a C++ Windows Runtime app, see Create your first Windows Runtime app using C++.
To ensure your app is network ready, you must set the capability in the project Package.appxmanifest file. For a definition of each network capability, see How to configure network isolation capabilities.
The following Background Transfer examples use C# and are based on the Background Transfer sample.
Namespace declarations
The following Windows Runtime and .NET namespaces are referenced in this topic.
using System;
using System.Collections.Generic;
using System.Net.HttpClient;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage.Pickers;
using Windows.Storage;
Downloading using HttpClient
First we create the HttpClient object. There are two properties values on the HttpClient object that may be worth reviewing to ensure it addresses the requirements of a request. The default value of the HttpClient.MaxResponseContentBufferSize property is 64K. If the size of the response exceeds this value it will result in an error.
An additional consideration to make is that, by default, a user-agent header not is sent with a HTTP request created using HttpClient. Some HTTP servers, including some Microsoft web servers, require that a user-agent header be included with the HTTP request sent from the client and will return an error if this header is not present.
To demonstrate how you would set these properties, in the following example we initialize an HttpClient object and define values for its MaxResponseContentBufferSize and DefaultRequestHeaders properties.
private HttpClient httpClient; public BlankPage() { this.InitializeComponent(); httpClient = new HttpClient(); // Increase the max buffer size for the response so we don't get an exception with so many web sites httpClient.MaxResponseContentBufferSize = 256000; httpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"); }
With HttpClient configured to generate a request that meets scenario requirements, passing a valid address to HttpClient.GetAsync will send the GET request. In this example the address is provided as a string using an input field in the UI, and the response body is displayed using an output field. The response to the request is represented by a HttpResponseMessage object, created when the HttpClient.GetAsync operation is completed. Additionally, calling HttpResponseMessage.EnsureSuccessStatusCode ensures that an exception is thrown if an error is returned in response to the GET request. On success, the requested data is represented by HttpResponseMessage.Content property
private async void Start_Click(object sender, RoutedEventArgs e) { try { string responseBodyAsText; OutputView.Text = ""; StatusText.Text = "Waiting for response ..."; HttpResponseMessage response = await httpClient.GetAsync(InputAddress.Text); response.EnsureSuccessStatusCode(); StatusText.Text = response.StatusCode + " " + response.ReasonPhrase + Environment.NewLine; responseBodyAsText = await response.Content.ReadAsStringAsync(); responseBodyAsText = responseBodyAsText.Replace("<br>", Environment.NewLine); // Insert new lines OutputView.Text = responseBodyAsText; } catch (HttpRequestException hre) { StatusText.Text = hre.ToString(); } catch (Exception ex) { // For debugging StatusText.Text = ex.ToString(); } }
Note Using the await keyword in C# and Visual Basic, the code for sending the GET request and retrieving the response asynchronously is similar to code that would be used to complete this operation synchronously. You can only use the await keyword if the method is defined as asynchronous using the async keyword.
Downloading a file using Background Transfer
When using Background Transfer each download exists as a DownloadOperation that exposes a number of control methods used to pause, resume, restart, and cancel the operation. App events (e.g. termination) and connectivity changes are handled automatically by the system per DownloadOperation; downloads will continue during app suspension or pause and persist beyond app termination. Additionally, setting the CostPolicy property will indicate whether or not your app will begin or continue downloads while a metered network is being used for Internet connectivity.
The following examples will walk you through the creation and initialization of a basic download, and how to enumerate and reintroduce operations persisted from a previous app session.
Configure and create a download
To demonstrate the configuration and creation of a new download, we've created an example class that creates a new DownloadOperation object using the BackgroundDownloader class when passed the required StorageFile and Uri values. The provided fileName string is used by createFileAsync to create the StorageFile in the Pictures folder. The uriString string representing the location of the web resource is used to create the Uri object.
With the download configured, HandleDownloadAsync is called to attach progress and completion handlers to the operation before initiating the download.
private async void StartDownload_Click(object sender, RoutedEventArgs e) { try { Uri source = new Uri(serverAddressField.Text.Trim()); string destination = fileNameField.Text.Trim(); if (destination == "") { Log("A local file name is required."); return; } StorageFile destinationFile = await KnownFolders.PicturesLibrary.CreateFileAsync( destination, CreationCollisionOption.GenerateUniqueName); BackgroundDownloader downloader = new BackgroundDownloader(); DownloadOperation download = downloader.CreateDownload(source, destinationFile); Log(String.Format("Downloading {0} to {1}, {2}", source.AbsoluteUri, destinationFile.Name, download.Guid)); // Attach progress and completion handlers. HandleDownloadAsync(download, true); } catch (Exception ex) { LogException("Download Error", ex); } }
Note Alternatively, your app can implement the FileSavePicker class to enable the end-user to input the file name and save location through the UI.
Attaching progress handlers and starting a download
This is the HandleDownloadAsync example method which does the actual work of attaching handlers to new DownloadOperations before calling startAsync to initiate them, or re-attaching handlers to DownloadOperations that have persisted to the current app session. Additionally it is used to reports status throughout the operation lifetime and remove a DownloadOperation from the activeDownloads list on completion or cancellation.
private async void HandleDownloadAsync(DownloadOperation download, bool start) { try { // Store the download so we can pause/resume. activeDownloads.Add(download); Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress); if (start) { // Start the download and attach a progress handler. await download.StartAsync().AsTask(cts.Token, progressCallback); } else { // The download was already running when the application started, re-attach the progress handler. await download.AttachAsync().AsTask(cts.Token, progressCallback); } ResponseInformation response = download.GetResponseInformation(); Log(String.Format("Completed: {0}, Status Code: {1}", download.Guid, response.StatusCode)); } catch (TaskCanceledException) { Log("Download cancelled."); } catch (Exception ex) { LogException("Error", ex); } finally { activeDownloads.Remove(download); } }
Note There are methods referred to in this example that define app behavior depending on the current progress of a download. For examples of this behavior, download the Background Transfer sample.
Enumerate persisted operations at start-up
The following code enumerates all DownloadOperation and iterates through the list, calling HandleDownloadAsync, a method defined later in this topic, to re-attach progress and completion handlers. This enumeration task only needs to be carried out at the beginning of an app session, as only app terminations during transfers introduce this scenario.
activeDownloads = new List<DownloadOperation>(); IReadOnlyList<DownloadOperation> downloads = await BackgroundDownloader.GetCurrentDownloadsAsync(); if (downloads.Count > 0) { List<Task> tasks = new List<Task>(); foreach (DownloadOperation download in downloads) { // Attach progress and completion handlers. tasks.Add(HandleDownloadAsync(download, false)); } }
Note
For Windows Phone Store apps, background transfers continue to progress while your app is not in the foreground. Because your app is not running in this scenario, it will not receive a notification when the transfer completes. When your app resumes, if you just check the progress of a completed transfer, the status will be BackgroundTransferStatus.Running. However, when you attach to the transfer as in the example code above, the task completion handler will be raised and the transfer status will be updated.
Request Timeouts
There are two primary connection timeout scenarios to take into consideration:
When establishing a new connection for a transfer, the connection request is aborted if it is not established within five minutes.
After a connection has been established, an HTTP request message that has not received a response within two minutes is aborted.
Note In either scenario, assuming there is Internet connectivity, Background Transfer will retry a request up to three times automatically. In the event Internet connectivity is not detected, additional requests will wait until it is.
Debugging Guidance
Stopping a debugging session in Microsoft Visual Studio is comparable to closing your app. Even while debugging, your app should enumerate and then, resume, restart, or cancel any downloads persisted from the previous session. For example, you can have your app cancel enumerated persisted download operations at app startup if there is no interest in previous operations for the current debug session.
If there are Visual Studio project updates, like changes to the app manifest, and the app is uninstalled and re-deployed, GetCurrentUploadsAsync cannot enumerate operations created using the previous app deployment.
See Debugging and testing Windows Store apps for more information.
When using Background Transfer during development, you may get into a situation where the internal caches of active and completed transfer operations can get out of sync. This may result in the inability to start new transfer operations or interact with existing operations and BackgroundTransferGroup objects. In some cases, attempting to interact with existing operations may trigger a crash. This result can occur if the TransferBehavior property is set to Parallel. This issue occurs only in certain scenarios during development and is not applicable to end users of your app.
Four scenarios using Visual Studio can cause this issue.
- You create a new project with the same app name as an existing project, but a different language (from C++ to C#, for example).
- You change the target architecture (from x86 to x64, for example) in an existing project.
- You change the culture (from neutral to en-US, for example) in an existing project.
- You add or remove a capability in the package manifest (adding Enterprise Authentication, for example) in an existing project.
Regular app servicing, including manifest updates which add or remove capabilities, do not trigger this issue on end user deployments of your app.
To work around this issue, completely uninstall all versions of the app and re-deploy with the new language, architecture, culture, or capability. This can be done via the Start screen or using PowerShell and the Remove-AppxPackage cmdlet.
Summary and next steps
In this topic we reviewed how to download files using the HttpClient (C#) and Background Transfer (C#/C++) APIs. We reviewed the differences between the two and highlighted how practical application depends on the size and lifetime of a file transfer.
You can also use Background Transfer to upload files. For an explanation of core concepts and examples, see Quickstart: Uploading a file.
Related topics
Other
Create your first Windows Runtime app using C# or Visual Basic
Create your first Windows Runtime app using C++
How to configure network capabilities
How to handle exceptions in network apps
Transferring data in the background
Reference
Windows.Networking.BackgroundTransfer
Samples