Introducción

Es muy común que para nuestras aplicaciones Web necesitemos ofrecer a los usuarios la capacidad de subir archivos al servidor por diversas razones: para su posterior procesamiento, respaldo de información, etc.  En este artículo veremos cómo en Silverlight esta tarea es sencilla por medio de la clase WebClient.

WebClient.OpenWriteAsync()

La clase WebClient permite una comunicación asíncrona entre el aplicativo Silverlight y algún endpoint que deseemos leer o escribir.  Es precisamente por medio de su método OpenWriteAsync por el cual podemos establecer un Stream de escritura para poder grabar en el servidor algún archivo que el usuario seleccione en la aplicación por medio de la caja de diálogo OpenFileDialog.  Es de vital importancia el mencionar que Silverlight, al ejecutar bajo un esquema de confianza media (Medium Trust) debido a que corre en un SandBox, no es capaz de acceder el sistema de archivos de la máquina en donde está ejecutando la aplicación, sino solamente a través de la autorización del usuario por medio de la caja de diálogo mencionada.

Bajo estas premisas, desarrollaremos la siguiente solución que demuestra lo siguiente:

  1. El uso de la clase OpenFileDialog para permitir al usuario seleccionar algún archivo que quiera subir al servidor.
  2. El uso de la clase WebClient y su método OpenWriteAsync para poder escribir en un Stream la secuencia de bytes que representen dicho archivo.
  3. La creación de una página .aspx que obtenga el Stream y escriba el archivo en el servidor

La aplicación tendrá la siguiente interfaz de usuario:

<UserControl x:Class="SilverlightApplication4.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="100">
    <Grid x:Name="LayoutRoot" Background="Azure">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button x:Name="btnUpload" 
                Content="Subir archivo..." 
                Width="100" 
                Height="30"
                HorizontalAlignment="Left"
                Margin="5"
                />
        <TextBlock x:Name="txtFileName" 
                   HorizontalAlignment="Left"
                   Margin="5"
                   Grid.Row="1" />
    </Grid>
</UserControl>

El botón llamado btnUpload tendrá la responsabilidad de mostrar la caja de diálogo OpenFileDialog para que el usuario selecciones un archivo de su sistema de archivos local.  El archivo seleccionado está representado con la propiedad File del objeto OpenFileDialog, y utilizaremos el método OpenRead() para leerlo como un Stream.  Posteriormente guardaremos en una variable llamada bytes la secuencia de bytes del archivo.

El siguiente paso será crear el manejador del evento OpenWriteCompleted, evento que se dispara cuando se haya creado efectivamente el Stream de escritura en el servidor.  Es en este manejador de evento en donde escribiremos la secuencia de bytes del archivo (variable llamada bytes) en el Stream.

Posteriormente ejecutaremos el método OpenWriteAsync() de la clase WebClient, el cual nos permitirá crear un Stream de escritura en el endpoint especificado en el argumento.  En el caso de nuestra aplicación usaremos Upload.aspx, el cual es una página de ASP.NET que además le mandaremos como parámetro el nombre del archivo seleccionado para evitar tener un nombre de archivo ‘hard-coded’.

El siguiente fragmento de codigo muestra esta funcionalidad completa:

btnUpload.Click += (sender, args) =>
    {
 
        OpenFileDialog ofd = new OpenFileDialog();
 
        //Abre la caja de diálogo
        if (ofd.ShowDialog().Value)
        {
            string fileName = ofd.File.Name;
            txtFileName.Text = fileName;
            Stream archivo = ofd.File.OpenRead();
            byte[] bytes = new byte[archivo.Length];
            archivo.Read(bytes, 0, bytes.Length);
 
 
            WebClient client = new WebClient();
            client.WriteStreamClosed += (s, a) =>
            {
                if (a.Error == null)
                {
                    //El archivo fue subido correctamente
                }
            };
 
            client.OpenWriteCompleted += (s, a) =>
            {
                if (a.Error == null)
                {
                    //El Stream ha sido abierto correctamente
                    //Escribe la secuencia de bytes en el Stream abierto
 
                    Stream stream = a.Result;
                    stream.Write(bytes, 0, bytes.Length);
                    stream.Flush();
                    stream.Close();
                }
            };
 
            //Abre un Stream de escritura
            client.OpenWriteAsync(new Uri(string.Format("/Upload.aspx?n={0}", fileName),
                UriKind.RelativeOrAbsolute));
        }
 
    };

Upload.aspx

En este ejemplo estamos usando una página de ASP.NET llamada Upload.aspx, la cual obtiene el Stream que se escribió en el código anteriormente descrito, y que servirá para efectivamente guardar ese Stream como un archivo en el servidor.  El nombre para el archivo es determinado a partir del valor del parámetro n en el querystring.  En el caso de esta solución el archivo se escribirá en el fólder ClientBin en el servidor, aunque podría obviamente ser parametrizable la localización en el servidor (tal vez a través de otro parámetro en el querystring).

El siguiente bloque muestra el código completo de Upload.aspx:

protected void Page_Load(object sender, EventArgs e)
        {
            //Nombre del archivo
            string fileName = string.Empty;
 
            try
            {
 
                if (Request.QueryString["n"] != null)
                    fileName = Request.QueryString["n"];
                else 
                    return;
 
                Stream inputStream = Request.InputStream;
                byte[] bytes = new byte[inputStream.Length];
 
                //Guarda el archivo en el fólder ClientBin
                StreamWriter sw = new StreamWriter(Server.MapPath("/ClientBin/") + fileName);
                BinaryWriter bw = new BinaryWriter(sw.BaseStream);
                inputStream.Read(bytes, 0, bytes.Length);
                bw.Write(bytes);
                bw.Flush();
                bw.Close();
 
            }
            catch (Exception) { throw; }
 
        }

Debemos mencionar que también sería posible reemplazar la página .aspx por una Handler .ashx e implementar en dicho archivo la funcionalidad descrita en el bloque de código anterior, también leyendo el Stream, obteniendo la secuencia de bytes y escribirla en el sistema de archivos.

Finalmente, la siguiente ilustración muestra la aplicación finalizada en donde podemos hacer lo siguiente:

  1. Hacer clic en el botón de Silverlight llamado ‘Subir archivo…’, esto invoca la caja de diálogo de Abrir archivo (Figura 1)
  2. Seleccionar el archivo y seleccionar OK (Figura 2)
  3. Se muestra el archivo seleccionado ya subido al servidor, en el fólder ClientBin el cual en este caso es el Sitio de Origen.

Resumen

Subir archivos al servidor es muy común en las aplicaciones Web y las aplicaciones de tipo RIA no están excluidas de ello.  En este artículo vimos cómo al utilizar las clases WebClient y OpenFileDialog podemos lograr esta funcionalidad en Silverlight y utilizar una página .aspx como puente para lograrlo.