miércoles, 27 de agosto de 2014

El Demo del Día: Descargar Archivos Asincronamente con WebClient

Descargar Archivos Asincronamente con WebClient

Requerimiento

Muchas veces los Administradores de Red bloquean las descargas de Archivos en los Navegadores y a veces necesitamos con urgencia bajar uno o mas archivos (de cualquier tipo). Si conocemos las direcciones de descarga podemos crear una aplicación que baje dichos archivos.

Características de la Aplicación Windows

1. Debe permitir bajar varios archivos desde URLs conocidas.
2. La descarga debe ser asíncrona sin bloquear el trabajo de la pantalla.
3. Se debe mostrar el progreso de la descarga en % y en forma gráfica con una barra de progreso.

Solución

Crearemos una aplicación Windows Forms en C# que use la clase WebClient del espacio de nombres "System.Net" que tiene un método asíncrono llamado "DownloadFileAsync". Además mostraremos el progreso en el evento: "DownloadProgressChanged" y actualizaremos el estado a Finalizado en el evento "DownloadFileCompleted".

Crear la Aplicación Windows Forms en C#

Crear un proyecto llamado "DescargarArchivos" en C# como una "Aplicación de Windows Forms".
Cambiar de nombre al formulario por: "frmDescarga", configurar su tamaño a 650 de ancho por 400 de alto, centrarlo y arrastrar 3 controles:
- Un TextBox llamado "txtURL"
- Un Button llamado "btnDescargar"
- Un ListView llamado "lvwProgreso"

El diseño del formulario debe quedar como se muestra a continuación:


Nota: El botón debe estar deshabilitado y solo se debe habilitar al ingresar una URL sobre el TextBox.

Escribir el siguiente código en el formulario "frmDescarga":

using System;
using System.Net;
using System.Net.Mime; //ContentDisposition
using System.IO;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;

namespace DescargarArchivos
{
    public partial class frmDescarga : Form
    {
        private string archivoFin="";

        public frmDescarga()
        {
            InitializeComponent();
        }

        private void configurarListView(object sender, EventArgs e)
        {
            lvwProgreso.FullRowSelect = true;
            lvwProgreso.GridLines = true;
            lvwProgreso.HotTracking = true;
            lvwProgreso.Columns.Add("Nombre Archivo", 200);
            lvwProgreso.Columns.Add("MB", 70, HorizontalAlignment.Center);
            lvwProgreso.Columns.Add("%", 40, HorizontalAlignment.Center);
            lvwProgreso.Columns.Add("Progreso", 200);
            lvwProgreso.Columns.Add("Estado", 80, HorizontalAlignment.Center);
            lvwProgreso.View = View.Details;
        }

        private void habilitarBotonDescargar(object sender, EventArgs e)
        {
            btnDescargar.Enabled = (!txtURL.Text.Equals(""));
        }

        private void iniciarDescarga(object sender, EventArgs e)
        {
            WebClient oCliente = new WebClient();
            Uri url = new Uri(txtURL.Text);
            string archivo = Path.GetFileName(url.AbsolutePath);
            ServicePointManager.DefaultConnectionLimit = 10;
            ListViewItem fila = lvwProgreso.Items.Add(archivo);
            fila.Name = archivo;
            fila.SubItems.Add("0");
            fila.SubItems.Add("0");
            fila.SubItems.Add("");
            fila.SubItems.Add("Iniciado");
            Rectangle rec = fila.SubItems[3].Bounds;
            ProgressBar pbr =new ProgressBar();
            pbr.Name = archivo;
            pbr.Parent = lvwProgreso;
            pbr.SetBounds(rec.X, rec.Y, rec.Width, rec.Height);
            pbr.Visible = true;
            txtURL.Clear();
            oCliente.DownloadProgressChanged +=
            new DownloadProgressChangedEventHandler(mostrarProgreso);
            oCliente.DownloadFileCompleted +=
            new AsyncCompletedEventHandler(finalizarDescarga);
            oCliente.DownloadFileAsync(url, archivo, archivo);
        }

        private void mostrarProgreso(object sender, DownloadProgressChangedEventArgs e)
        {
            int bytesRecibidos = (int)(e.BytesReceived / (1024 * 1024));
            int bytesTotales = (int)(e.TotalBytesToReceive / (1024 * 1024));
            string archivo = e.UserState.ToString();
            Control[] rpta = lvwProgreso.Controls.Find(archivo, false);
            if(rpta!=null&&rpta.Length > 0){
                ProgressBar pbr = (ProgressBar)rpta[0];
                lvwProgreso.Items[archivo].SubItems[1].Text = String.Format("{0} - {1}",
                bytesRecibidos, bytesTotales);
                lvwProgreso.Items[archivo].SubItems[2].Text = e.ProgressPercentage.ToString();
                pbr.Value = e.ProgressPercentage;
                lvwProgreso.Items[archivo].SubItems[4].Text = "Descargando";
            }
        }

        private void finalizarDescarga(object sender, AsyncCompletedEventArgs e)
        {
            archivoFin = e.UserState.ToString();
            MethodInvoker mi = new MethodInvoker(mostrarResultado);
            this.Invoke(mi);
        }

        private void mostrarResultado()
        {
            lvwProgreso.Items[archivoFin].SubItems[4].Text = "Finalizado";
        }
    }
}

Nota: Una de las dificultades de la aplicación fue como actualizar la Fila del ListView adecuada, ya que son varios archivos los que se descargan, para eso a cada barra y a cada ListViewItem se le dio como nombre el archivo que se descargaba y se uso como identificador para obtenerlos.

Otra dificultad que encontré fue que la clase WebClient no podía descargar varios a la vez, para lo cual se uso la propiedad "DefaultConnectionLimit" de la clase "ServicePointManager", en este caso configurada para bajar 10 archivos.

Probar la Aplicación de Descarga de Archivos

Grabar la aplicación, compilarla y ejecutarla con F5. Se mostrará una ventana como la siguiente:


Ingresar una dirección URL conteniendo un archivo y clic en el botón "Descargar", copiar varias direcciones e iniciar sus descarga, se irá mostrando el progreso de cada descarga y su estado, tal como se ve en la siguiente figura:


Cuando la descarga finaliza se completa la barra, se cambia el estado a finalizado y el archivo estará disponible para ejecutarse, por defecto en la carpeta donde se encuentra la aplicación (bin\debug).

Comentario Final

En este post vimos como bajar varios archivos simultáneamente usando WebClient sin bloquear la pantalla, es decir en forma asíncrona, mostrando el progreso. Espero les guste y se animen a hacer sus propios utilitarios y no bajarlos de otros.

Descarga:
Demo17_DescargarArchivos_WebClient

El Libro del Día: Expert C# 5.0

El Libro del Día: 2014-08-27

Titulo: Expert C# 5.0
Autor: Mohammad Rahman
Editorial: Apress
Nro Paginas: 609

Capítulos:
Chapter 1: Reintroducing C#:-A Detailed Look at the Language We All Know
Chapter 2: C# Objects in Memory
Chapter 3: Parameters
Chapter 4: Methods
Chapter 5: Automatic Property Declaration
Chapter 6: Enum
Chapter 7: Delegate
Chapter 8: Event
Chapter 9: Foreach and Iterator
Chapter 10: The String Data Type
Chapter 11: Collections Explained
Chapter 12: Linq in C#
Chapter 13: Exception Management
Chapter 14: Asynchrony
Chapter 15: Diagnostic Tools in .NET

Descarga:
Expert_C#_5.0