Uploading (or downloading) large files to your web server has always been a dangerous process. Too many users with large files can cause server overload, users trying to upload files beyond the accepted server size, giving reliable feedback to the user while uploading and handling file interruptions are common causes. So when I had to build uploading large files into a UWP app I really didn’t want to rely on the standard HTTP Post method. Thankfully there is an awesome API built in to the framework called Background Transfer.

Windows.Networking.BackgroundTransfer

Background Transfer has some really great features that tie in really well with the UWP framework, such as:

  • Background processing even if the app is suspended or terminated
  • Network sensitive by automatically suspending and then resuming when the network is available again
  • Data Sense and Battery Sense aware
  • Each transfer runs in a separate process
  • Progress information is easy to hook into
  • Full control over the process with pause, resume and cancel

Uploading files

Before we can write our UWP code we are going to need a server to upload the file to:

  • Create a new website project in Visual Studio: File->New Web Site…
  • Create an ASP.NET Empty Web Site
  • Create a new web form called Upload.aspx
  • Open Upload.aspx.vb and paste in the following code:
    Partial Class Upload
        Inherits System.Web.UI.Page
    
        Private Sub Upload_Load(sender As Object, e As EventArgs) Handles Me.Load
            Try
                'check if our header has the filename
                If Request.Headers("Filename") IsNot Nothing Then
                    Dim fileName As String = Request.Headers("Filename")
                    'show the filename we are uploading
                    Response.Write(Convert.ToString("Filename is ") & fileName)
    
                    'set the save location
                    Dim saveLocation As String = Convert.ToString(Server.MapPath("UploadedData") + "\") & fileName
    
                    'read the file and save it
                    Using fs As New System.IO.FileStream(saveLocation, System.IO.FileMode.Create)
                        Request.InputStream.CopyTo(fs)
                    End Using
                End If
            Catch ex As Exception
                Response.StatusCode = 500
                Response.StatusDescription = ex.Message
                Response.End()
            End Try
    
        End Sub
    End Class
    
  • Create a new folder in the root of your web project called UploadedData. This where our uploaded files will be saved.
  • Run your website project and take note of the URL including the port number when it launches in your browser:
  • Edit Web.config and add maxRequestLength=”122880″ to httpRuntime:
    <configuration>
        <system.web>
          <compilation debug="true" strict="false" explicit="true" targetFramework="4.5.2" />
          <httpRuntime targetFramework="4.5.2" maxRequestLength="122880" />
        </system.web>
    </configuration>
    

SUPER IMPORTANT
Don’t use the above code in a production environment. The server code will accept any file uploaded to it and this can pose a serious security risk. You should authenticate any uploads before they begin and also make sure you handle any uploaded files correctly such as filtering file types etc.

Sending our file to the server

Now we can write our upload code for our UWP app:

Imports Windows.Networking.BackgroundTransfer
Imports Windows.Storage
Imports Windows.Storage.Pickers
Imports System.Threading

' used to track the upload task
Private CancelToken As CancellationTokenSource

    Private Async Sub DoUpload()
        'our web server address for processing uploads
        Dim ServerAddress As String = "http://localhost:30485/Upload.aspx"
        Dim upURI As Uri = Nothing

        'create a URI from our server address
        If Not Uri.TryCreate(ServerAddress, UriKind.Absolute, upURI) Then
            ' invalid URI
            Stop
        End If

        'ask the user to select a file to upload
        Dim picker As New FileOpenPicker
        picker.FileTypeFilter.Add("*")
        Dim UpFile As StorageFile = Await picker.PickSingleFileAsync()

        'initiate our upload background task
        Dim Uploader As BackgroundUploader = New BackgroundUploader()
        'Add a header to our post to pass the filename
        Uploader.SetRequestHeader("Filename", UpFile.Name)

        'create an upload operation
        Dim UploadOp As UploadOperation = Uploader.CreateUpload(upURI, UpFile)

        'define our progress callback to call the UploadProgress Sub
        Dim progressCallback As Progress(Of UploadOperation) = New Progress(Of UploadOperation)(AddressOf UploadProgress)

        ' create a new cancel token in case we need to cancel the process
        CancelToken = New CancellationTokenSource()

        'start the upload
        Await UploadOp.StartAsync().AsTask(CancelToken.Token, progressCallback)
        Dim response As ResponseInformation = UploadOp.GetResponseInformation()
        UpdateProgressStatus(String.Format("Completed: {0}, Status Code: {1}",
                                            UploadOp.Guid,
                                            response.StatusCode))

    End Sub


    Private Sub UploadProgress(UploadOp As UploadOperation)

        'make a copy of the current progress as it is on
        'ongoing operation. We need its status in this point of time

        Dim CurrentProgress As BackgroundUploadProgress = UploadOp.Progress
        Dim Status As String = String.Format("Progress: {0}, Status: {1}",
                                             UploadOp.Guid,
                                             CurrentProgress.Status)

        Dim PercentSent As Double = 100
        If CurrentProgress.TotalBytesToSend > 0 Then
            PercentSent = CurrentProgress.BytesSent * 100 / CurrentProgress.TotalBytesToSend
        End If

        Status &= String.Format(" - Sent bytes: {0} of {1} ({2}%), Received bytes: {3} of {4}",
                                CurrentProgress.BytesSent,
                                CurrentProgress.TotalBytesToSend,
                                PercentSent, CurrentProgress.BytesReceived,
                                CurrentProgress.TotalBytesToReceive)

        If CurrentProgress.HasRestarted Then
            Status &= " - Upload restarted"
        End If

        If CurrentProgress.HasResponseChanged Then
            Status &= " - Response updated; Header count: " & UploadOp.GetResponseInformation().Headers.Count
        End If

        ' This sub is called on a background thread so we cannot access the UI directly, we need to Marshal the update.
        Marshal_UpdateProgressStatus(Status)
    End Sub

    Private Sub Marshal_UpdateProgressStatus(outText As String)

        Dim ignoreAction = Me.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
                                                  Sub()
                                                      UpdateProgressStatus(outText)
                                                  End Sub)
    End Sub

    Private Sub UpdateProgressStatus(outText As String)
        'updates the textblock defined in our XML <TextBlock x:Name="txtProgress" Text="" />
        txtProgress.Text = outText
    End Sub


You will need to expand the above with your own functionality such as controlling which file the user can upload, checking the file size and processing the file on the server after its uploaded etc.

Handling the Background Task

If you look at line 55 in the above code you can see we are getting the status of the task by calling CurrentProgress.Status. You can handle the status here but remember that the UploadProgress sub is called on a thread outside of the UI so you’ll need to be careful what you do here.
You’ll also see on line 41 we are getting the response back from the server so you can check that too.

Checking for current Background Tasks

If the user navigates away from the app or even if the current frame and then comes back you are going to need to show them any background tasks that are currently still going.

You’ll need to check for any active transfers when your app or frame is navigated to for example. Call the CheckForActiveTransfers function which will in turn hook into any active tasks via the ResumeBackgroundTasks function:

    Protected Overrides Async Sub OnNavigatedTo(e As NavigationEventArgs)
        Await CheckForActiveTransfers()
    End Sub

    Private Async Function CheckForActiveTransfers() As Task
        Dim UploadOps As IReadOnlyList(Of UploadOperation) = Nothing
        Try
            UploadOps = Await BackgroundUploader.GetCurrentUploadsAsync()
        Catch ex As Exception
            'handle error
            Return
        End Try
        If UploadOps.Count > 0 Then
            CancelToken = New CancellationTokenSource()
            Dim tasks As List(Of Task) = New List(Of Task)()
            For Each UploadOp In UploadOps
                tasks.Add(ResumeBackgroundTasks(UploadOp))
            Next

            Await Task.WhenAll(tasks)
        End If
    End Function

    Private Async Function ResumeBackgroundTasks(upload As UploadOperation) As Task
        Try
            Dim progressCallback As Progress(Of UploadOperation) = New Progress(Of UploadOperation)(AddressOf UploadProgress)
            Await upload.AttachAsync().AsTask(CancelToken.Token, progressCallback)

            Dim response As ResponseInformation = upload.GetResponseInformation()
            UpdateProgressStatus(String.Format("Completed: {0}, Status Code: {1}",
                                               upload.Guid,
                                               response.StatusCode))
        Catch ex As TaskCanceledException
            'handle task cancelled
        Catch ex As Exception
            'handle resume error
        End Try
    End Function

Cancel the Background Transfer

If you need to cancel the background transfer call this sub:

   Private Sub CancelBackgroundTasks()
        CancelToken.Cancel()
        CancelToken.Dispose()
        CancelToken = New CancellationTokenSource()
    End Sub