Télécharger des fichiers peut, à première vue, sembler facile à coder..... mais lorsqu'on souhaite y apporter des fonctionnalités dans le but d'obtenir un gestionnaire "avancé" de téléchargement de fichiers, cela peut s'avérer plus compliquer !
Dans ce billet il était question de créer un tel gestionnaire mais pour un soucis de ré-utilisabilité, le débat m'a amené à développer en toute modestie une classe en tenant compte des spécificités suivantes :
- Télécharger des fichiers et les enregistrer dans un répertoire de destination de son choix
- Affichage de la progression dans une progressbar et un label (indiquant l'url en cours)
- A la toute fin du téléchargement, afficher le résultat des réussites et des échecs !
- Séparer la mise à jour de l'UI des traitements
- Créer une librairie de classe pouvant être utilisée dans n'importe quel projet
Voilà à quoi pourrait ressembler une telle librairie :
1- Création d'une structure permettant de définir les informations du fichier à télécharger :
2- Création d'une classe permettant d'ajouter des urls (+ infos) en liste d'attente pour le téléchargement :
3- Création d'une classe qui permettra de connaitre le statut (réussite ou échec + message d'erreur) de chaque fichier téléchargé à la toute fin de tache :
4- Création d'une classe qui hérite de la classe EventArgs pour connaitre le statut des fichiers téléchargés à la toute fin de la tache :
5- Création de la classe principale dans laquelle on permet la mise à jour de la partie UI côté utilisateur au moyen de 2 eventHandler :
6- Enfin l'appel de la classe ,depuis l'application cliente ou formulaire principal (nécessite 1 bouton, 1 progressbar, 1 Label et bien sur le référencement de la librairie portant l'espace de nom "Download" ) :
A+
Dans ce billet il était question de créer un tel gestionnaire mais pour un soucis de ré-utilisabilité, le débat m'a amené à développer en toute modestie une classe en tenant compte des spécificités suivantes :
- Télécharger des fichiers et les enregistrer dans un répertoire de destination de son choix
- Affichage de la progression dans une progressbar et un label (indiquant l'url en cours)
- A la toute fin du téléchargement, afficher le résultat des réussites et des échecs !
- Séparer la mise à jour de l'UI des traitements
- Créer une librairie de classe pouvant être utilisée dans n'importe quel projet
Voilà à quoi pourrait ressembler une telle librairie :
1- Création d'une structure permettant de définir les informations du fichier à télécharger :
Public Structure DownloadInfos Public Name As String Public Url As String Public DestDir As String End Structure
2- Création d'une classe permettant d'ajouter des urls (+ infos) en liste d'attente pour le téléchargement :
Public Class DownloadList #Region " Fields " Private m_list As Queue(Of DownloadInfos) #End Region #Region " Methods " Sub New() m_list = New Queue(Of DownloadInfos) End Sub Public Sub Enqueue(item As DownloadInfos) m_list.Enqueue(item) End Sub Public Function Any() As Boolean Return Enumerable.Any(Of DownloadInfos)(m_list) End Function Public Function Dequeue() As DownloadInfos Return m_list.Dequeue() End Function Public Sub Clear() m_list.Clear() End Sub #End Region End Class
3- Création d'une classe qui permettra de connaitre le statut (réussite ou échec + message d'erreur) de chaque fichier téléchargé à la toute fin de tache :
Imports System.Text Public Class DownloadResult #Region " Enumerations " Enum State Erreur = 0 Completed = 1 End Enum #End Region #Region " Fields " Private ReadOnly m_url As String Private ReadOnly m_etat As State Private ReadOnly m_message As String #End Region #Region " Properties " Public ReadOnly Property Url As String Get Return m_url End Get End Property Public ReadOnly Property Etat As State Get Return m_etat End Get End Property Public ReadOnly Property Message As String Get Return m_message End Get End Property #End Region #Region " Methods " Sub New(url$, etat As State, message$) m_url = url m_etat = etat m_message = message End Sub Public Overrides Function ToString() As String If (Me.Etat = State.Completed) Then Return ("Téléchargement réussi : " & Me.m_url) End If Return ("Téléchargement en échec : " & Me.m_url) End Function #End Region End Class
4- Création d'une classe qui hérite de la classe EventArgs pour connaitre le statut des fichiers téléchargés à la toute fin de la tache :
Imports System.Text Public Class TerminatedEventArgs Inherits EventArgs #Region " Fields " Private m_Result As IEnumerable(Of DownloadResult) #End Region #Region " Properties " Public ReadOnly Property Result As IEnumerable(Of DownloadResult) Get Return m_Result End Get End Property #End Region #Region " Methods " Public Sub New(Result As IEnumerable(Of DownloadResult)) m_Result = Result End Sub Public Overrides Function ToString() As String Return String.Join(Environment.NewLine, m_Result) End Function #End Region End Class
5- Création de la classe principale dans laquelle on permet la mise à jour de la partie UI côté utilisateur au moyen de 2 eventHandler :
Imports System.Net Imports System.IO Imports System.ComponentModel Public Class Downloader Implements IDisposable #Region " Fields " Private WithEvents wc As WebClient Private downloadUrls As DownloadList Private results As List(Of DownloadResult) Private m_filesCount As Integer #End Region #Region " Properties " Public ReadOnly Property FilesCount As Integer Get Return m_filesCount End Get End Property #End Region #Region " Events " Public Event ProgressChanged As ProgressChangedDelegate Public Event Terminated(sender As Object, e As TerminatedEventArgs) #End Region #Region " Delegates " Public Delegate Sub ProgressChangedDelegate(sender As Object, e As DownloadProgressChangedEventArgs) #End Region #Region " Methods " Public Sub New() wc = New WebClient downloadUrls = New DownloadList results = New List(Of DownloadResult) m_filesCount = 0 End Sub Public Sub AddInfos(Infos As DownloadInfos) downloadUrls.Enqueue(Infos) m_filesCount += 1 End Sub Public Sub DownloadFiles() 'On vérifie que la liste des urls n'est pas vide avant de lancer le téléchargement (vérification basée sur la taille de la liste intiale donc après l'appel de la méthode Enqueue) If m_filesCount <> 0 Then ' On vérifie que la liste n'est pas vide à chaque lancement de la routine If downloadUrls.Any() Then With wc 'L'url "s" est retournée et ensuite supprimée de la liste avec la méthode Dequeue Dim s = downloadUrls.Dequeue() ' On s'attarde ici à tout ce qui pourrait être utile en vue de constituer le chemin de destination du fichier téléchargé ! Dim fiName As New FileInfo(New Uri(s.Url).AbsolutePath) ' A noter que le 3ème argument permet de savoir côté UI quel fichier est en cours de téléchargement ! .DownloadFileAsync(New Uri(s.Url), s.DestDir & "\" & fiName.Name, s) End With Return Else ' C'est ici qu'on déclenche un event pour dire que le téléchargement de tous les fichiers est bien terminé ! ' On en profite pour passer le résultat final des téléchargements (réussi et échec) en argument de l'event Terminated ! RaiseEvent Terminated(Me, New TerminatedEventArgs(results)) ClearLists() End If End If End Sub Private Sub ClearLists() 'On vide les listes pour la prochaine utilisation de la fonction DownloadFiles downloadUrls.Clear() results.Clear() End Sub Private Sub DownloadFiles_DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs) Handles wc.DownloadProgressChanged ' On déclenche l'event ProgressChangedEvent pour permettre la MAJ de la progressbar côté UI RaiseEvent ProgressChanged(sender, e) End Sub Private Sub DownloadFiles_DownloadFileCompleted(sender As Object, e As AsyncCompletedEventArgs) Handles wc.DownloadFileCompleted ' On masque cet event côté UI car il permet de savoir à quel moment se termine la fin du téléchargement de chaque fichier ! If Not e.UserState Is Nothing Then Dim Infos = DirectCast(e.UserState, DownloadInfos) If e.Error IsNot Nothing Then results.Add(New DownloadResult(Infos.Url, DownloadResult.State.Erreur, e.Error.ToString)) Else results.Add(New DownloadResult(Infos.Url, DownloadResult.State.Completed, "Completed")) End If End If DownloadFiles() End Sub #End Region #Region "IDisposable Support" Private disposedValue As Boolean Protected Overridable Sub Dispose(disposing As Boolean) If Not Me.disposedValue Then If disposing Then wc.Dispose() End If End If Me.disposedValue = True End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class
6- Enfin l'appel de la classe ,depuis l'application cliente ou formulaire principal (nécessite 1 bouton, 1 progressbar, 1 Label et bien sur le référencement de la librairie portant l'espace de nom "Download" ) :
Imports System.Net Imports System.ComponentModel Imports Download Public Class Form1 Private WithEvents DL As New Downloader Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Using DL Dim url1 As New DownloadInfos() With {.Name = "GC", .Url = "http://wallace87000.upd.fr/Gif Animation Creator.exe", .DestDir = "D:\Sauvegardes"} DL.AddInfos(url1) Dim url2 As New DownloadInfos() With {.Name = "IU", .Url = "http://wallace87000.upd.fr/ImaboxUploader.exe", .DestDir = "D:\Sauvegardes"} DL.AddInfos(url2) DL.DownloadFiles() End Using End Sub Private Sub DL_DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs) Handles DL.ProgressChanged ProgressBar1.Value = e.ProgressPercentage Label1.Text = DirectCast(e.UserState, DownloadInfos).Url End Sub Private Sub DL_DownloadTerminated(sender As Object, e As TerminatedEventArgs) Handles DL.Terminated MsgBox(e.ToString) End Sub End Class
A+