Get File Size on FTP Server and put it on a Label

The main problems (set Option Strict On to find more):
You can’t access the UI objects from a thread different than the UI Thread.

The error you receive is:

Cross-thread operation not valid:
Control lblFileSizeD accessed from
a thread other than the thread it was created on

Then, the same error for lblDownloadedBytesD.

Also, you are eating up your Error messages using an empty handler with:

Catch ex As Exception

End Try

This nullifies any handling, because there’s none. You are simply letting the code run past it without taking any action. The handlers are there to, well, handle the errors, not to let them go unchecked.

When you need to access and update some UI component property, use the BackGroundWorker ReportProgress() method. This method has an overload that accepts a parameter of type Object. Meaning, you can feed it anything. This Object will be the e.UserState property in the ReportProgress ProgressChangedEventArgs class.

The .RunWorkerAsync() method also accepts an Object parameter. This Object will become the e.Argument property of the BackgroundWorker.DoWork Event. This gives some flexibility in relation to the parameters you can actually pass to your BackGroundWorker.

One more problem: the Ftp Download procedure does not support cancellation. When run, a user can’t stop it.

Last problem: as reported in the documentation, you should never reference the BackGroundWorker object you instantiated in your UI thread (the Form) in its DoWork event. Use the sender object and cast it to the BackGroundWorker class.

In this example, all the UI references are delegated to a Class object that is passed to the DoWork event through the RunWorkerAsync(Object) method (using the e.Argument property).
The Class object is updated with progress details and then fed to the ReportProgress(Int32, Object) method, which runs in the original Synchronization Context (the UI thread, where the RunWorkerAsync method is called).
The UI can be updated safely. No cross-thread operations can occur.

A cancellation method is also implemented. This allows to abort the download procedure and to delete a partial downloaded file, if one is created.

The error handling is minimal, but this is something you need to integrate with your own tools.

(I’ve used the same names for the UI Controls, it should be easier to test.)

Imports System.ComponentModel
Imports System.Globalization
Imports System.IO
Imports System.Net
Imports System.Net.Security
Imports System.Security.Cryptography.X509Certificates

Public Class frmBGWorkerDownload

    Friend WithEvents BWorkerD As BackgroundWorker
    Public Sub New()
        InitializeComponent()
        BWorkerD = New BackgroundWorker()
        BWorkerD.WorkerReportsProgress = True
        BWorkerD.WorkerSupportsCancellation = True

        AddHandler BWorkerD.DoWork, AddressOf BWorkerD_DoWork
        AddHandler BWorkerD.ProgressChanged, AddressOf BWorkerD_ProgressChanged
        AddHandler BWorkerD.RunWorkerCompleted, AddressOf BWorkerD_RunWorkerCompleted
        BWorkerD.RunWorkerAsync(BGWorkerObj)
    End Sub

    Private Class BGWorkerObject
        Public Property UserName As String
        Public Property Password As String
        Public Property ResourceURI As String
        Public Property FilePath As String
        Public Property FileLength As Long
        Public Property DownloadedBytes As Long
        Public Property BytesToDownload As Long
    End Class

    Private Sub btnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click
        pBarD.Value = 0
        Dim BGWorkerObj As BGWorkerObject = New BGWorkerObject With {
            .ResourceURI = txtFilePathD.Text,
            .FilePath = Path.Combine(txtSavePathD.Text, Path.GetFileName(txtFilePathD.Text)),
            .UserName = txtFTPUsernameD.Text,
            .Password = txtFTPPasswordD.Text
        }
    End Sub

    Private Sub BWorkerD_DoWork(sender As Object, e As DoWorkEventArgs)
        Dim BGW As BackgroundWorker = TryCast(sender, BackgroundWorker)
        Dim BGWorkerObj As BGWorkerObject = TryCast(e.Argument, BGWorkerObject)
        Dim FTPRequest As FtpWebRequest
        Dim BufferSize As Integer = 131072

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
        ServicePointManager.ServerCertificateValidationCallback =
            Function(snd As Object, Cert As X509Certificate, Chain As X509Chain, Err As SslPolicyErrors)
                Return True
            End Function

        FTPRequest = DirectCast(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
        FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
        'FTPRequest.Method = WebRequestMethods.Ftp.GetFileSize
        '----------------------- UPDATE  ------------------------
        FTPRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails
        '--------------------- END UPDATE  ------------------------
        FTPRequest.EnableSsl = True

        '----------------------- UPDATE  ------------------------
        Using FtpResponse As WebResponse = FTPRequest.GetResponse, 
          DirListStream As Stream = FtpResponse.GetResponseStream(), 
          listReader As StreamReader = New StreamReader(DirListStream)
            While Not listReader.EndOfStream
                Dim DirContent As String = listReader.ReadLine()
                If DirContent.Contains(Path.GetFileNameWithoutExtension(BGWorkerObj.ResourceURI)) Then
                    BGWorkerObj.FileLength = Convert.ToInt64(DirContent.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)(4))
                    BGW.ReportProgress(0, BGWorkerObj)
                    Exit While
                End If
            End While
        End Using
        '----------------------- END UPDATE  ------------------------

        'Using FtpResponse As WebResponse = FTPRequest.GetResponse
        '    BGWorkerObj.FileLength = Convert.ToInt64(FtpResponse.ContentLength)
        '    BGW.ReportProgress(0, BGWorkerObj)
        'End Using

        If BGW.CancellationPending Then e.Cancel = True

        Try
            FTPRequest = CType(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
            FTPRequest.EnableSsl = True
            FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
            FTPRequest.Method = WebRequestMethods.Ftp.DownloadFile

            Using Response As FtpWebResponse = DirectCast(FTPRequest.GetResponse, FtpWebResponse)
                If Response.StatusCode > 299 Then
                    e.Result = 0
                    Throw New Exception("The Ftp Server rejected the request. StatusCode: " &
                                        Response.StatusCode.ToString(),
                                        New InvalidOperationException(Response.StatusCode.ToString()))
                    Exit Sub
                End If
                Using stream = Response.GetResponseStream(), 
                  fileStream As FileStream = File.Create(BGWorkerObj.FilePath)
                    Dim read As Integer
                    Dim buffer As Byte() = New Byte(BufferSize - 1) {}
                    Do
                        read = stream.Read(buffer, 0, buffer.Length)
                        fileStream.Write(buffer, 0, read)
                        BGWorkerObj.DownloadedBytes += read
                        BGWorkerObj.BytesToDownload = BGWorkerObj.FileLength - BGWorkerObj.DownloadedBytes

                        If BGW.CancellationPending Then
                            e.Cancel = True
                            Exit Do
                        Else
                            BGW.ReportProgress(CInt((CSng(BGWorkerObj.DownloadedBytes) / BGWorkerObj.FileLength) * 100), BGWorkerObj)
                        End If
                    Loop While read > 0
                End Using
            End Using

        Catch ex As Exception
            If e.Cancel = False Then Throw
        Finally
            If e.Cancel = True Then
                If File.Exists(BGWorkerObj.FilePath) Then
                    File.Delete(BGWorkerObj.FilePath)
                End If
            End If
        End Try

    End Sub

    Private Sub BWorkerD_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
         pBarD.Value = e.ProgressPercentage
        lblPercentD.Text = e.ProgressPercentage.ToString() & " %"

        If lblFileSizeD.Text.Length = 0 Then
            lblFileSizeD.Text = CType(e.UserState, BGWorkerObject).FileLength.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
        End If
        lblDownloadedBytesD.Text = CType(e.UserState, BGWorkerObject).DownloadedBytes.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
        If e.ProgressPercentage <= 15 Then
            lblDownloadedBytesD.ForeColor = Color.Red
        ElseIf e.ProgressPercentage <= 66 Then
            lblDownloadedBytesD.ForeColor = Color.Orange
        Else
            lblDownloadedBytesD.ForeColor = Color.LightGreen
        End If
    End Sub

    Private Sub BWorkerD_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
        Dim DownloadAborted As Boolean = False
        If e.Error IsNot Nothing Then
            DownloadAborted = True
            lblDownloadedBytesD.ForeColor = Color.Red
            lblDownloadedBytesD.Text = "Error!"
        ElseIf e.Cancelled Then
            DownloadAborted = True
            lblDownloadedBytesD.ForeColor = Color.Yellow
            lblDownloadedBytesD.Text = "Cancelled!"
            pBarD.Value = 0
            lblPercentD.Text = "0%"
        Else
            lblDownloadedBytesD.ForeColor = Color.LightGreen
            lblDownloadedBytesD.Text = "Download completed"
        End If
    End Sub

    Private Sub btnAbortDownload_Click(sender As Object, e As EventArgs) Handles btnAbortDownload.Click
        BWorkerD.CancelAsync()
    End Sub
End Class

A visual result of the operation described:

frmBGWorkerDownload Image

A PasteBin of the Form’s Designer + Code

Leave a Comment