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

5 comentarios:

  1. Genial artículo profesor! Gracias por compartirlo!

    ResponderBorrar
  2. hola como que tipos de links se puede poner en el textbox

    ResponderBorrar
  3. Pedro, en el Textbox puedes colocar la URL de cualquier tipo de archivo: mp3, avi, jpg, pdf, lo que consigues ingresando a cualquier pagina y dando clic derecho sobre un enlace y seleccionando "Copia dirección de enlace" y luego la pegas en el TextBox y ha "descargar".

    ResponderBorrar
  4. Justo necesitaba este ejemplo! Muchas gracias profesor!

    ResponderBorrar
  5. Muchas gracias por el ejemplo, es justo lo que estaba buscando. Adapté tu código para ingresar varios link y asì descargar de un listado y está estupendo, ya no descargo de uno en uno :).

    Pero hasta ahora no consigo en limpiar las descargas que han finalizado y sólo dejar que se muestren las que están descargando.

    De nuevo gracias por compartir esta DEMO :) :D.
    Suerte en todo.




    ResponderBorrar