Adicionando archivos de Apps al Storage de Azure con Mobile Services

 

Los Windows Azure Mobile Services, proveen un mecanismo para permitir que clientes móviles (por ahora Windows 8, Windows Phone y iOS) se conecten a Windows Azure para almacenar y leer información tabular. Además ofrecen funcionalidad de push notifications, logging y autenticación.

Pero cómo benefician estos servicios a la necesidad de poder almacenar archivos en el storage?

En este post están los detalles que permiten justificar que para acceder a una cuenta de Storage desde un dispositivo es muy riesgoso dejar allí los datos de la cuenta de Azure, pues pueden ser extraídos con Ingeniería Inversa. Además de que el acceso se dificulta por falta de un API que abstraiga el acceso a REST directo. Windows Azure entonces dispone de un mecanismo llamado “Shared Access Signature” o SAS, que proporciona unas credenciales temporales para que se pueda acceder al storage.

Tener un servicio Web que reciba las imágenes y desde su memoria las suba al storage de Azure de manera segura, es una opción, pero muy poco escalable, porque puede que miles de usuarios quieran subir sus fotos. Disminuir la carga del web server haciendo solo que exponga las credenciales SAS a los dispositivos para que estos luego puedan acceder directa y fácilmente al storage es mejor opción, pero sigue siendo algo cara, pues requiere un web server y un desarrollo asociado, así como todo el overhead de mantenimiento.

Entonces la idea es aprovechar los Mobile Services para obtener esta SAS.

No obstante, en las funcionalidades que mencioné, en ninguna está la del suministro de la SAS. Pero aquí es donde la imaginación y el ingenio juegan parte importante. Lo que sí mencioné es que se puede acceder a unas tablas a leerlas o modificarlas. Para ello se han dispuesto unas APIs especialmente diseñadas para WP y WinRT; hasta para iOS hay versión. Y en las tres plataformas pueden ser incluidas y usadas sin ningún problema de funcionamiento ni certificación cuando la app se sube al store. Pues bien, las operaciones CRUD que hacemos sobre estas tablas (que se pueden diseñar desde el portal de administración de Azure sin código alguno), están sujetas a que se ejecuten triggers que se dispararán cuando nosotros lo especifiquemos. Por ejemplo, cuando haya una inserción. En últimas esto se puede tomar como un servicio web asíncrono, en el cual insertamos un request en la tabla y luego la volvemos a consultar para ver la respuesta obtenida, que obviamente se encontrará en otra columna.

El primer paso entonces, es crear el Mobile Service en el portal de Windows Azure. Esto es totalmente gratuito. Solo es requerido crear una cuenta, cosa que también es gratis. Un paso a paso muy bien elaborado lo pueden encontrar aquí.

Básicamente lo que hicimos en ese paso a paso, fue crear el servicio y luego desde el portal, usar unas funcionalidades muy útiles que tenemos allí, como la de crear automáticamente nuestra primera tabla de datos. Y luego bajarnos una solución preconstruida que va a consumir esa tabla. Esa solución puede ser para phone, Windows 8 o iOS.

clip_image002
Obviamente, también podemos conectarnos a apps existentes. No solo a nuevas. Con este fin el portal nos suministra un código con una clave que debe tener la app para poderse conectar. Este código en el caso de Windows 8 por ejemplo, se pone en el App.xaml.cs:

 //This MobileServiceClient has been configured to communicate
//with your Mobile Service's url and application key. 
//You're all set to start working with your Mobile Service!
public static MobileServiceClient MobileService =
   new MobileServiceClient(
      "https://warserv.azure-mobile.net/",
      "ElSmsvstPUsdXWsHJqFteqhkLxDVcdr15"
);

Obviamente para que el código nos funcione, debemos de haber referenciado la dll que contiene el tipo MobileServiceClient. Esta dll ya viene agregada si nos bajamos la solución de ejemplo del portal. Si estamos habilitando una solución preexistente, la incluimos manualmente. Se llama Windows.Azure.Mobile.Services.Managed.Client y queda disponible luego de que instalemos el Mobile Services SDK.

Después de esto, nuestra app ya queda lista para conectarse directamente con el servicio y empezar por ejemplo a hacer inserciones en la tabla que construimos en el paso a paso (o cualquier otra tabla que creemos dentro del servicio usando el portal).

Ahora lo que haremos es generar el trigger para nuestro servicio, de manera que cada vez que exista un insert, se dispare una rutina que nos retorne dentro de ese registro insertado, la SAS que necesitamos. Esta rutina la debemos escribir con Javascript, que se ejecutará en el server, a través de un editor online que tenemos en el portal. Para acceder a ese editor, ubicamos nuestro servicio y luego allí escogemos la tabla en cuestión (1). Después vamos a SCRIPT (2) y finalmente escogemos la operación (Insert) de esta manera, ejecutaremos la acción especificada en el script con cada insert que hagamos en la tabla.

clip_image003

El código del script lo pueden descargar de aquí. Y la parte más importante del mismo es ésta:

 function insert(item, user, request) {
    var accountName = '<SU NOMBRE DE CUENTA>';
    var accountKey = '<SU PAK>';

    //Note: this code assumes the container already 
    //exists in blob storage.
    //If you wish to dynamically create the container then implement
    //guidance here - 
    //https://msdn.microsoft.com/en-us/library/windowsazure/dd179468.aspx
    var container = 'test';
    var imageName = item.ImageName;

    item.SAS = getBlobSharedAccessSignature(accountName, 
        accountKey, container, imageName);
    request.execute();
}

Como ven, tienen que poner los datos de la cuenta en los placeholders. También se aprecia que el ítem insertado viene como parámetro. Y que luego a ese ítem se le ajusta la propiedad SAS con una función que recibe el nombre de la cuenta, la clave, el contenedor y la imagen. Esto nos va a retornar una SAS que no es más que una URL que contiene unos permisos temporales implícitos, para que luego a esa URL podamos subir un archivo de nombre item.ImageName. Al final, vemos como se ejecuta el request y es en este momento cuando se inserta el registro como tal en la tabla del servicio. Además el ítem actualizado baja al cliente automáticamente y desde allí podremos consultar el valor de la SAS.

Entonces, para el manejo desde nuestra app, necesitamos una clase que nos represente al ítem:

     //la estructura de la tabla que tenemos
    //en el Mobile Service. 
    //Es una clase generada por nosotros 
    //de acuerdo a nuestras necesidades.
    public class TodoItem
    {
        public int Id { get; set; }

        [DataMember(Name = "text")]
        public string Text { get; set; }

        [DataMember(Name = "ImageName")]
        public string ImageName { get; set; }

        //Es en este campo, donde quedará almacenada la SAS
        [DataMember(Name = "SAS")]
        public string SAS { get; set; }

        [DataMember(Name = "complete")]
        public bool Complete { get; set; }
    }

Esta clase la creamos con los campos por defecto que se crean las tablas en Mobile Services, y le agregamos los campos requeridos.

Luego de esto, cuando ya tenemos el archivo que queremos subir a la nube, lo que hacemos, es crear una inserción en la tabla:

 StorageFile file = await openPicker.PickSingleFileAsync();
if (file != null)
{
    //agrega un item a la tabla de Mobile Service, disparando un trigger
    //que retorna el item.SAS, como un valor en otra columna de la tabla,
    //que se transfiere automáticamente al ítem.                
    var todoItem = new TodoItem() { Text = "test image",
        ImageName = file.Name };
    await todoTable.InsertAsync(todoItem);
    items.Add(todoItem);

    //Cargamos la imagen con el HttpClient al blob 
    //service usando la SAS generada en item.SAS
    using (var client = new HttpClient())
    {
        //Obtenemos el stream de un storage file definido anteriormente
        using (var fileStream = await file.OpenStreamForReadAsync())
        {
            var content = new StreamContent(fileStream);
            content.Headers.Add("Content-Type", file.ContentType);
            content.Headers.Add("x-ms-blob-type", "BlockBlob");
        //Con el PutAsync, enviamos el archivo a Azure
        //a través de la URL autorizadora que está en SAS de la forma:
        //"https://--------------.blob.core.windows.net/test/
        //androidsmartglass.jpg?etc
        //donde etc es la cadena de validación
            using (var uploadResponse = 
                await client.PutAsync(new Uri(todoItem.SAS), content))
            {
            //Agregar cualquier post proceso adicional                
            }
        }
    }
}

De esta manera, ya habremos podido subir nuestro archivo al storage de Azure, desde un cliente móvil, sin arriesgar nuestra cuenta de storage, sin sobrecostos por Servidores Web y de una manera absolutamente escalable, porque este request en total pesa menos de 1Kb. Mucho menos de lo que sería subir la imagen a un servidor, para que desde allí de manera segura se pudiese enviar al storage. Ahora, el envío es directo, de manera que es más rápido!

En síntesis, hemos visto como entre las bondades de los Windows Azure Mobile Services, podemos contar con la capacidad de generar servicios sencillos basados en código de servidor escrito con Javascript que como input puede recibir un registro en las tablas de datos y como output una respuesta que queda almacenada en ellas en otra columna distinta. Si adicionamos a esto un trigger para cada inserción, obtendremos una respuesta asíncrona que hace que el Javascript que hemos escrito actuando sobre las APIs de Azure, permita entre otras cosas generar claves SAS, útiles para que dispositivos puedan acceder de manera segura y directa al storage.