How to implement background file transfers for Windows Phone 8
[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]
This topic walks you through creating a simple app that uses the BackgroundTransferService to initiate and monitor background file transfers. In this example, you will create two pages. The first page will list the status all of the app’s current background file transfers and allow the user to cancel any active transfers, both of which are requirements for all apps that use background transfers. The second page will allow the user to add new background transfers to the queue. Apps are required to allow the user to initiate background transfers, such as providing a Start download button. Otherwise, the app must alert the user that a transfer is being initiated on their behalf. This page will also allow the user to restrict the new background transfers to occur only when the device has a Wi-Fi connection. This is not required, but is highly recommended, especially if the files being transferred are large.
To create background file transfers, you will use the BackgroundTransferRequest object to represent a file transfer. The properties of this object allow you to specify the file to be downloaded or uploaded and the destination path for the transfer and the transfer method, in addition to other configurable settings. A BackgroundTransferRequest is passed to the Add method of the BackgroundTransferService object to initiate the transfer. You can also use the BackgroundTransferService object to retrieve BackgroundTransferRequest objects representing the currently registered transfers for your app. These objects can be used to determine the current status of an active transfer.
This topic contains the following sections.
Creating a page to list file transfers
The first page you will create in this example lists all of the background transfers currently registered for the app. This page will use a ListBox control that is data bound to a list of BackgroundTransferRequest objects.
To create a file transfer list page
In Visual Studio, create a new **Windows Phone App ** project. This template is in the Windows Phone category.
The first step in creating the transfer list page is to create the user interface in XAML. Because the XAML code necessary to create a good-looking UI can be bulky, the XAML code will be presented first and then the important elements will be highlighted. For in-depth information on using controls, see Controls for Windows Phone 8.
Paste the following code into the MainPage.xaml file in your project. The code should be pasted inside the Grid element that is named “ContentPanel”.
<TextBlock Text="you have no transfers registered" Name="EmptyTextBlock" Visibility="Collapsed"/> <ListBox Name="TransferListBox"> <ListBox.ItemTemplate> <DataTemplate> <Grid Background="Transparent" Margin="0,0,0,30"> <Grid.ColumnDefinitions> <ColumnDefinition Width="380"/> <ColumnDefinition Width="50"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0"> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Tag}" Foreground="{StaticResource PhoneAccentBrush}" FontWeight="Bold"/> <StackPanel Orientation="Horizontal"> <TextBlock Text="status: "/> <TextBlock Text="{Binding TransferStatus}" HorizontalAlignment="Right"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="bytes received: "/> <TextBlock Text="{Binding BytesReceived}" HorizontalAlignment="Right"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="total bytes: "/> <TextBlock Text="{Binding TotalBytesToReceive}" HorizontalAlignment="Right"/> </StackPanel> </StackPanel> </Grid> <Grid Grid.Column="1"> <Button Tag="{Binding RequestId}" Click="CancelButton_Click" Content="X" BorderBrush="Red" Background="Red" Foreground="{StaticResource PhoneBackgroundBrush}" VerticalAlignment="Top" BorderThickness="0" Width="50" Padding="0,0,0,0"></Button> </Grid> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Look at the code you pasted and take note of the following elements:
The first TextBlock control is used to display a message to the user when there are no background transfers to display. The C# code-behind page will toggle the Visibility property of this control to Visible when there are no items in the list, and Collapsed when there are items in the list to be displayed.
The ListBox element defines the control that will list all of the background transfers and their associated data. The rest of the XAML code doesn’t actually add items to the list. Instead, it contains a DataTemplate that tells the ListBox how to display the data that is bound to it. The ListBox name, TransferListBox, is the name that will be used to reference the control in the C# code-behind page.
The Grid and StackPanel elements are container controls that are used to organize the layout of the other controls.
The TextBlock elements inside of the StackPanel controls display the values of the properties in the BackgroundTransferRequest class. For example, the TransferStatus property indicates whether the transfer is waiting, transferring, or completed. The BytesReceived property tells you how many bytes have been received for the transfer. The Tag property allows you to attach custom data to the transfer request. In this example, the Tag property is used to pass the friendly name for the download. The syntax {Binding RecurrenceType} maps the Text property of each of these controls to the specified property name. Additional TextBlock controls are included to provide a label for each value.
Finally, a Button is added to allow the user to delete background transfer requests. This functionality is required for all apps that use background transfers. The RequestId property of a BackgroundTransferRequest is used to uniquely identify each transfer request. This value is bound to the Tag property of the Button so that the Click event handler for the button, RemoveButton_Click, can determine which transfer to delete. This handler will be added to the C# code-behind page later.
The last thing you need to add to MainPage.xaml is an ApplicationBar. The ApplicationBar will have an ApplicationBarIconButton that the user can click to go to the page that is used to add new transfers. Another button is used to allow the user to remove all completed transfers from the list. Paste the following code over the commented-out example ApplicationBar code that is included in the template. Make sure that you replace the comments as well so that this code isn’t commented out.
<phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/Images/add.png" Text="Add" Click="AddBackgroundTransferButton_Click"/> <shell:ApplicationBarIconButton IconUri="/Images/remove.png" Text="Cancel All" Click="CancelAllButton_Click"/> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
Now it is time to modify the code in MainPage.xaml.cs. First, you need to add a using directive to include the namespace that contains the BackgroundTransferRequest class. This app will move transferred files in isolated storage when they complete, so the System.IO.IsolatedStorage namespace is also needed.
using Microsoft.Phone.BackgroundTransfer; using System.IO.IsolatedStorage;
Next, add a class variable of type IEnumerable<BackgroundTransferRequest>. The “<BackgroundTransferRequest>” part of the variable type indicates that the IEnumerable object will contain BackgroundTransferRequest objects.
public partial class MainPage : PhoneApplicationPage { IEnumerable<BackgroundTransferRequest> transferRequests;
Sometimes background transfers will be in a pending state waiting for a condition to change. For example, transfers can be configured to only proceed when the device has a Wi-Fi connection. This app will alert the user that transfers are pending when the user navigates to the transfer status page. Declare the following four Boolean variables below the transferRequests declaration.
// Booleans for tracking if any transfers are waiting for user // action. bool WaitingForExternalPower; bool WaitingForExternalPowerDueToBatterySaverMode; bool WaitingForNonVoiceBlockingNetwork; bool WaitingForWiFi;
Create a method called UpdateRequestsList. This method uses the Requests property of the BackgroundTransferService class to retrieve the list of transfer requests currently registered for this app. This property returns a list of new BackgroundTransferRequest objects. Because the objects are new, make sure that you call Dispose on any existing object references to avoid leaking memory.
private void UpdateRequestsList() { // The Requests property returns new references, so make sure that // you dispose of the old references to avoid memory leaks. if (transferRequests != null) { foreach (var request in transferRequests) { request.Dispose(); } } transferRequests = BackgroundTransferService.Requests; }
Create a method called UpdateUI. This method uses the UpdateRequestsList method that was just defined to obtain the BackgroundTransferRequest objects that are registered with the BackgroundTransferService for this app. The ItemsSource property of the ListBox is set to the IEnumerable class variable containing the list of transfers. This forces the ListBox to update itself. Finally, if the ListBox contains one or more items, the text block that informs the user that no transfers are registered is collapsed; otherwise, it is made visible. Paste the following method definition inside the MainPage class definition.
private void UpdateUI() { // Update the list of transfer requests UpdateRequestsList(); // Update the TransferListBox with the list of transfer requests. TransferListBox.ItemsSource = transferRequests; // If there are 1 or more transfers, hide the "no transfers" // TextBlock. IF there are zero transfers, show the TextBlock. if (TransferListBox.Items.Count > 0) { EmptyTextBlock.Visibility = Visibility.Collapsed; } else { EmptyTextBlock.Visibility = Visibility.Visible; } }
Override the OnNavigatedTo(NavigationEventArgs) method of the page’s PhoneApplicationPage base class. This method is called whenever the user navigates to the page, including when the app is launched and the page is first displayed. In this method, the Boolean values used to monitor if any transfers are pending are set to false. Next, a helper method that will be defined in the following steps, InitialTransferStatusCheck, will be called. At the end of the method, UpdateUI is called to show the current transfer status to the user.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { // Reset all of the user action Booleans on page load. WaitingForExternalPower = false; WaitingForExternalPowerDueToBatterySaverMode = false; WaitingForNonVoiceBlockingNetwork = false; WaitingForWiFi = false; // When the page loads, refresh the list of file transfers. InitialTransferStatusCheck(); UpdateUI(); }
Create the InitialTransferStatusCheck helper method. First, this calls the UpdateRequestsList helper method to obtain the current list of transfer requests. Then, for each BackgroundTransferRequest object in the list, a handler for the TransferStatusChanged and TransferProgressChanged events are assigned. These handlers will be defined later. Next, the helper method ProcessTransfer is called for each transfer request. This method will handle moving transferred files that have completed and removing them from the queue. It will also set the corresponding Boolean value if any of the transfers are pending. After calling ProcessTransfer for each transfer request, if any transfers are pending, a message box is displayed to the user.
private void InitialTransferStatusCheck() { UpdateRequestsList(); foreach (var transfer in transferRequests) { transfer.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferStatusChanged); transfer.TransferProgressChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferProgressChanged); ProcessTransfer(transfer); } if (WaitingForExternalPower) { MessageBox.Show("You have one or more file transfers waiting for external power. Connect your device to external power to continue transferring."); } if (WaitingForExternalPowerDueToBatterySaverMode) { MessageBox.Show("You have one or more file transfers waiting for external power. Connect your device to external power or disable Battery Saver Mode to continue transferring."); } if (WaitingForNonVoiceBlockingNetwork) { MessageBox.Show("You have one or more file transfers waiting for a network that supports simultaneous voice and data."); } if (WaitingForWiFi) { MessageBox.Show("You have one or more file transfers waiting for a WiFi connection. Connect your device to a WiFi network to continue transferring."); } }
The helper method ProcessTransfer takes a BackgroundTransferRequest object as a parameter and then takes action, depending on the status of the transfer. The status of the transfer is determined using the TransferStatus property. If the status is Completed, it means that the transfer is no longer active, but it does not mean that the transfer was successful. To determine the success of a completed transfer, evaluate the StatusCode property which contains the HTTP status code for the transfer. Depending on the server configuration, a successful transfer will have a status of 200 or 206.
If the transfer is successful, the app removes the transfer request from the queue with by calling the helper method RemoveTransferRequest. Each app is limited to 25 transfer requests in the queue at any time, and the system does not automatically remove completed transfers from the queue, so it is important that your app remove completed transfers to free up slots for more transfers. Next, the successfully transferred file is moved out of the “/shared/transfers” directory to which all transfers must be downloaded.
If the completed transfer was not successful, because it returned a status code other than 200 or 206, your app may attempt to take some action to resolve the issue, but it is important that you remove the transfer from the queue. In this example, the TransferError message is checked to determine whether a large file transfer was attempted on battery power, and if so, the request is requeued.
For the cases where the transfer request being processed has a TransferStatus that indicates that the transfer is pending, waiting for user action, the appropriate Boolean value is set so that a message box can be displayed to the user.
private void ProcessTransfer(BackgroundTransferRequest transfer) { switch(transfer.TransferStatus) { case TransferStatus.Completed: // If the status code of a completed transfer is 200 or 206, the // transfer was successful if (transfer.StatusCode == 200 || transfer.StatusCode == 206) { // Remove the transfer request in order to make room in the // queue for more transfers. Transfers are not automatically // removed by the system. RemoveTransferRequest(transfer.RequestId); // In this example, the downloaded file is moved into the root // Isolated Storage directory using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) { string filename = transfer.Tag; if (isoStore.FileExists(filename)) { isoStore.DeleteFile(filename); } isoStore.MoveFile(transfer.DownloadLocation.OriginalString, filename); } } else { // This is where you can handle whatever error is indicated by the // StatusCode and then remove the transfer from the queue. RemoveTransferRequest(transfer.RequestId); if (transfer.TransferError != null) { // Handle TransferError if one exists. } } break; case TransferStatus.WaitingForExternalPower: WaitingForExternalPower = true; break; case TransferStatus.WaitingForExternalPowerDueToBatterySaverMode: WaitingForExternalPowerDueToBatterySaverMode = true; break; case TransferStatus.WaitingForNonVoiceBlockingNetwork: WaitingForNonVoiceBlockingNetwork = true; break; case TransferStatus.WaitingForWiFi: WaitingForWiFi = true; break; } }
Next, the event handlers for the TransferStatusChanged and TransferProgressChanged are implemented. TransferStatusChanged is called when a transfer changes status, such as from Transferring to Completed. The previously defined helper method ProcessTransfer is called to process the transfer, and then UpdateUI is called. TransferProgressChanged is called when the number of bytes transferred changes. This handler simply calls UpdateUI.
void transfer_TransferStatusChanged(object sender, BackgroundTransferEventArgs e) { ProcessTransfer(e.Request); UpdateUI(); } void transfer_TransferProgressChanged(object sender, BackgroundTransferEventArgs e) { UpdateUI(); }
Apps that use background transfers are required to provide a mechanism for the user to cancel background transfer requests. In the XAML definition for this page, a cancel button was created for each background transfer request. All of the buttons share the same event handler. The Tag property of each button is used to pass the unique RequestId of each transfer to the Click event handler. A helper method, RemoveTransferRequest, is called to remove the associated transfer. The definition of this method will be shown next. After the transfer request is removed, UpdateUI is called.
private void CancelButton_Click(object sender, EventArgs e) { // The ID for each transfer request is bound to the // Tag property of each Remove button. string transferID = ((Button)sender).Tag as string; // Delete the transfer request RemoveTransferRequest(transferID); // Refresh the list of file transfers UpdateUI(); }
The following helper method takes the RequestId of a transfer and calls Remove(BackgroundTransferRequest) to unregister the transfer request with the service.
private void RemoveTransferRequest(string transferID) { // Use Find to retrieve the transfer request with the specified ID. BackgroundTransferRequest transferToRemove = BackgroundTransferService.Find(transferID); // Try to remove the transfer from the background transfer service. try { BackgroundTransferService.Remove(transferToRemove); } catch (Exception e) { // Handle the exception. } }
In the XAML for this page, an ApplicationBarIconButton button was created to allow the user to remove all completed transfers from the app’s list. In the Click handler for this button, loop over the transfers in the list. If the TransferStatus of the transfer is Completed, call the RemoveTransferRequest helper method to remove it. Finally, call UpdateUI.
private void CancelAllButton_Click(object sender, EventArgs e) { // Loop through the list of transfer requests foreach (var transfer in BackgroundTransferService.Requests) { RemoveTransferRequest(transfer.RequestId); } // Refresh the list of file transfer requests. UpdateUI(); }
The last update to MainPage.xaml.cs is to create the Click event handler for the ApplicationBarIconButton that the user taps to add a new background transfer. The Navigate method is used to navigate the app to AddBackgroundTransfer.xaml, which will be implemented in the next section.
private void AddBackgroundTransferButton_Click(object sender, EventArgs e) { // Navigate to the AddBackgroundTransfer page when the Add button // is tapped. NavigationService.Navigate(new Uri("/AddBackgroundTransfer.xaml", UriKind.RelativeOrAbsolute)); }
Creating a page to add background transfers
The next step in this walkthrough is to create a page that allows users to add background transfers to the queue. Apps that use background transfers must either alert the user that background transfers are being performed or to let the user initiate the transfers. This example will display a hard-coded list of large remote files. For each address, there will be an Add button that will create a background transfer for the file when clicked.
To create a page to add background transfers
Add a new page to your project. From the Project menu, select Add New Item…. Select Windows Phone Portrait Page. In the Name text box, type AddBackgroundTransfer.xaml.
In Solution Explorer, double-click AddBackgroundTransfer.xaml to open it. Next, add the controls that allow the user to select files to transfer. Paste the following code into AddBackgroundTransfer.xaml. The code should be pasted inside the Grid element that is named “ContentPanel”.
<StackPanel> <StackPanel Orientation="Horizontal"> <CheckBox Name="wifiOnlyCheckbox" IsChecked="True" ></CheckBox> <TextBlock Text="only download when WiFi is available." VerticalAlignment="Center" Foreground="{StaticResource PhoneAccentBrush}"></TextBlock> </StackPanel> <StackPanel Orientation="Horizontal"> <CheckBox Name="externalPowerOnlyCheckbox" IsChecked="True" ></CheckBox> <TextBlock Text="only download when connected to external power." VerticalAlignment="Center" Foreground="{StaticResource PhoneAccentBrush}" TextWrapping="Wrap" Height="60" Width="374"></TextBlock> </StackPanel> <ListBox Name="URLListBox" > <ListBox.ItemTemplate> <DataTemplate> <Grid Background="Transparent" Margin="0,0,0,30"> <Grid.ColumnDefinitions> <ColumnDefinition Width="350"/> <ColumnDefinition Width="80"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0"> <TextBlock Text="{Binding}" TextWrapping="Wrap" HorizontalAlignment="Left"/> </Grid> <Grid Grid.Column="1"> <Button Tag="{Binding}" Content="Add" Height="72" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="80" FontSize="15" Click="addButton_Click"/> </Grid> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel>
Just like the XAML in MainPage.xaml, this code defines a ListBox and defines a template that will be used to display the items in the list. In this case, a TextBlock and a Button control are added for each item. The Text property of the TextBlock and the Tag property of the button are bound to the values in an array of URLs that will be defined in the C# code-behind page.
Also included in this XAML code are two CheckBox controls that are used to allow the user to choose for transfers to be executed only when the device has a Wi-Fi connection and external power. We recommend that all apps provide this functionality, especially if the transferred files are large.
In AddBackgroundTransfer.xaml.cs, add using directives for the BackgroundTransfer and IsolatedStorage namespaces.
using Microsoft.Phone.BackgroundTransfer; using System.IO.IsolatedStorage;
Define a class variable that is a list of the URLs for the files for which the user will be able to add background transfers. Most apps will obtain URLs for files to download dynamically from a web service. To make this example self-contained, these URLs are hard-coded. Paste this definition just inside the page class definition as shown.
public partial class AddBackgroundTransfer : PhoneApplicationPage { private readonly List<string> urls = new List<string> { "https://www.contoso.com/assets/cms/images/samples/windowsphonetestfile1.png", "https://www.contoso.com/assets/cms/images/samples/windowsphonetestfile2.png", "https://www.contoso.com/assets/cms/images/samples/windowsphonetestfile3.png", "https://www.contoso.com/assets/cms/images/samples/windowsphonetestfile4.png", "https://www.contoso.com/assets/cms/images/samples/windowsphonetestfile5.png", };
In the constructor for the page, first set the ItemsSource property of the ListBox that was defined in XAML to the list of URLs that was just created. Then, check to make sure that a directory named “/shared/transfers” exists in the root of isolated storage, and if not, then create the directory. All background transfers must be performed within this directory. The DownloadLocation and UploadLocation properties of the BackgroundTransferRequest object must point to a location within this directory. You can create subdirectories within this directory if you choose, but attempting to save a file to or upload a file from a directory outside of the “/shared/transfers” directory will throw an exception.
public AddBackgroundTransfer() { InitializeComponent(); // Bind the list of URLs to the ListBox. URLListBox.ItemsSource = urls; // Make sure that the required "/shared/transfers" directory exists // in isolated storage. using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoStore.DirectoryExists("/shared/transfers")) { isoStore.CreateDirectory("/shared/transfers"); } } }
For each URL displayed to the user, an Add button is created. All of these buttons share the same event handler, addButton_Click. All of the remaining code in this section will be placed inside of this handler.
private void addButton_Click(object sender, RoutedEventArgs e) { // The following code goes here. }
For each Add button in the UI, the Tag property is bound to the URL of the file to be downloaded. Each app is limited to 25 concurrent transfer requests at a time. The first step in the Click event handler is to check to see if this limit has been reached. If so, either alert the user so that they can decide to wait or cancel existing transfers. Or, you could save the transfer information and add it to the queue at a later time when slots are available. Next, get the URL string and create a URI object from it. If the URL string has not already been escaped, do so using EscapeUriString. Pass this URI to the constructor for BackgroundTransferRequest. Next, set the transfer method. This example uses GET, but POST is also supported.
// Check to see if the maximum number of requests per app has been exceeded. if (BackgroundTransferService.Requests.Count() >= 25) { // Note: Instead of showing a message to the user, you could store the // requested file URI in isolated storage and add it to the queue later. MessageBox.Show("The maximum number of background file transfer requests for this application has been exceeded. "); return; } // Get the URI of the file to be transferred from the Tag property // of the button that was clicked. string transferFileName = ((Button)sender).Tag as string; Uri transferUri = new Uri(Uri.EscapeUriString(transferFileName), UriKind.RelativeOrAbsolute); // Create the new transfer request, passing in the URI of the file to // be transferred. BackgroundTransferRequest transferRequest = new BackgroundTransferRequest(transferUri); // Set the transfer method. GET and POST are supported. transferRequest.Method = "GET";
Next, extract the file name from the end of the URL. Create a URI within the “transfers” directory in isolated storage and set the DownloadLocation property. The Tag property can be used to pass custom data along with the transfer request. In this example, Tag is used to pass the friendly name of the transfer file.
// Get the file name from the end of the transfer URI and create a local URI // in the "transfers" directory in isolated storage. string downloadFile = transferFileName.Substring(transferFileName.LastIndexOf("/") + 1); Uri downloadUri = new Uri("shared/transfers/" + downloadFile, UriKind.RelativeOrAbsolute); transferRequest.DownloadLocation = downloadUri; // Pass custom data with the Tag property. In this example, the friendly name // is passed. transferRequest.Tag = downloadFile;
Next, check the IsChecked property of the CheckBox controls to see whether the user would prefer that transfers execute only when a Wi-Fi connection or external power is present. Use the TransferPreferences property to specify requirements for both data connection and device power source.
// If the Wi-Fi-only check box is not checked, then set the TransferPreferences // to allow transfers over a cellular connection. if (wifiOnlyCheckbox.IsChecked == false) { transferRequest.TransferPreferences = TransferPreferences.AllowCellular; } if (externalPowerOnlyCheckbox.IsChecked == false) { transferRequest.TransferPreferences = TransferPreferences.AllowBattery; } if (wifiOnlyCheckbox.IsChecked == false && externalPowerOnlyCheckbox.IsChecked == false) { transferRequest.TransferPreferences = TransferPreferences.AllowCellularAndBattery; }
Finally, register the transfer request by calling the Add(BackgroundTransferRequest) method of the BackgroundTransferService. It is possible for an exception to be thrown when the transfer request is added, so call the method inside a try block.
// Add the transfer request using the BackgroundTransferService. Do this in // a try block in case an exception is thrown. try { BackgroundTransferService.Add(transferRequest); } catch (InvalidOperationException ex) { MessageBox.Show("Unable to add background transfer request. " + ex.Message); } catch (Exception) { MessageBox.Show("Unable to add background transfer request."); }