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
Recent Comments