Uno de los servicios disponibles en la plataforma Windows Azure trata varios tipos de almacenamiento no relacional y es conocido como Windows Azure Storage.
Podemos decir que cada blob es en realidad un archivo con una URI independiente dentro del cloud. Cada uno de estos blobs debe estar alojado obligatoriamente dentro de un contenedor en concreto. Estos contenedores pueden almacenar uno o más archivos, los cuales no tienen que ser necesariamente del mismo tipo:
En cuanto a las URIs que referencian a cada uno de los blobs, siguen un patrón en función de tres valores fundamentales: storageAccount, container y blobName.
http://<storageAccount>.blob.core.windows.net/<container>/<blobName>
Por ejemplo, una URI válida para uno de los archivos mostrados en la imagen anterior podría ser la siguiente:
http://mediastorage.blob.core.windows.net/pop/Fly.mp3
Para verlo con un ejemplo vamos a crear una aplicación que almacene nuestras imágenes en un contenedor llamado mypics. En primer lugar creamos un nuevo proyecto ASP.NET Web Application llamado Blobs.
Abrimos el archivo Default.aspx donde añadiremos el siguiente código, que nos permitirá seleccionar cualquier archivo que queramos adjuntar, además de visualizar los que ya han sido almacenados en el contenedor a través de un gridView:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="Blobs._Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<div id="divUpload">
<asp:FileUpload ID="fuScript" runat="server" />
<asp:Button ID="btnUpload" runat="server" Text="Upload!" OnClick="btnUpload_Click" />
</div>
<div class="picsData">
<asp:GridView ID="grvMyPics" runat="server" DataKeyNames="Uri" AutoGenerateDeleteButton="true"
AutoGenerateColumns="false" OnRowDeleting="grvMyPics_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="Container">
<ItemTemplate>
<%# Eval("Container.Name")%>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField HeaderText="Uri" DataField="Uri" />
<asp:ImageField HeaderText="Pic" DataImageUrlField="Uri" />
</Columns>
</asp:GridView>
</div>
</asp:Content>
La página es bastante sencilla: En primer lugar tenemos un apartado donde podemos seleccionar una imagen de nuestro equipo local y subirla a un blob de nuestro storage. En el segundo apartado se nos mostrarán los archivos ya almacenados.
Antes de implementar la funcionalidad para los dos casos anteriores, debemos añadir una referencia a Microsoft.WindowsAzure.StorageClient.dll la cual se trata de un wrapper que nos facilitará las llamadas a la API REST que se comunica con el servicio Windows Azure Storage.
Ruta: C:\Program Files\Windows Azure SDK\v1.4\ref
Como funcionalidad tendremos lo siguiente:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Blobs.WindowsAzureServices;
namespace Blobs
{
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
BindFiles();
}
protected void btnUpload_Click(object sender, EventArgs e)
{
if (fuScript.HasFile)
{
new BlobService().AddBlob(fuScript.FileBytes, fuScript.FileName);
BindFiles();
}
}
private void BindFiles()
{
grvMyPics.DataSource = new BlobService().GetBlobs();
grvMyPics.DataBind();
}
protected void grvMyPics_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
var uri = (Uri)grvMyPics.DataKeys[e.RowIndex].Value;
new BlobService().DeleteBlob(uri.AbsoluteUri);
BindFiles();
}
}
}
En el Page_Load vamos a lanzar el método BindFiles, declarado más abajo, el cual nos va a cargar el gridView con los blobs que hayan sido almacenados. Para ello, se ha creado una clase llamada BlobService donde vamos a declarar una serie de métodos que se encargarán de la llamada a la API a través del wrapper.
Si volvemos a fijarnos en el código de la página Default.aspx, vemos que existe un método enlazado al botón btnUpload en su evento click. En él vamos a comprobar si el usuario añadió algún archivo y, de ser así, utilizaremos de nuevo BlobService para incluir el mismo dentro de un blob. Una vez añadido, llamaremos de nuevo a BindFiles para refrescar la información del grid.
Por último, se ha declarado un último método asociado al evento RowDeleting del grid con el objetivo de eliminar el blob seleccionado haciendo un último uso de la clase mencionada anteriormente.
En cuanto al código de BlobService, se ha creado una clase independiente con el objetivo de simplificar la lógica:
using System.Collections.Generic;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace Blobs.WindowsAzureServices
{
public class BlobService
{
private readonly CloudBlobClient _blobClient;
private const string ContainerName = "mypics";
public BlobService()
{
var account = CloudStorageAccount.DevelopmentStorageAccount;
_blobClient = new CloudBlobClient(account.BlobEndpoint, account.Credentials);
}
private CloudBlobContainer GetContainer(string containerName)
{
var container = _blobClient.GetContainerReference(containerName);
if (container.CreateIfNotExist())
{
var containerPermissions = container.GetPermissions();
containerPermissions.PublicAccess = BlobContainerPublicAccessType.Container;
container.SetPermissions(containerPermissions);
}
return container;
}
public void AddBlob(byte[] fileBytes, string fileName)
{
var blob = GetContainer(ContainerName).GetBlobReference(fileName);
blob.UploadByteArray(fileBytes);
}
public IEnumerable<IListBlobItem> GetBlobs()
{
var container = GetContainer(ContainerName);
return container.ListBlobs();
}
public void DeleteBlob(string uri)
{
var blob = GetContainer(ContainerName).GetBlobReference(uri);
blob.DeleteIfExists();
}
}
}
Si nos fijamos en el constructor de la clase, vemos que necesitamos dos elementos que trabajan de forma conjunta: Por un lado debemos recuperar la cuenta con la que vamos a trabajar, donde caben dos opciones: En primer lugar podemos hacer uso de la cuenta de desarrollo, la cual es una simulación del storage que está alojado en el cloud. Esta simulación necesita ejecutar una aplicación llamada Storage Emulator la cual nos proporciona tres endpoints para cada uno de los servicios disponibles en Windows Azure Storage:
Por otro lado, podemos utilizar una cuenta real que apunte a nuestro servicio ya en la nube, del cual hablaremos al final de este artículo :-)
Una vez recuperada la cuenta, creamos un objeto del tipo CloudBlobClient el cual nos proporcionará los métodos necesarios para contactar con la API.
El primer paso a seguir en cualquiera de las acciones, Añadir, eliminar, listar, etcétera es recuperar el contenedor sobre el que vamos a operar. Para ello, se ha creado un método llamado GetContainer donde rescatamos en este caso el contenedor mypics y lo creamos en el caso de que no exista. Otro punto muy importante en relación con el contenedor trata del nivel de acceso que queramos asignarle. A día de hoy existen tres niveles de acceso:
- Blob: La información de un blob dentro de un contenedor puede ser leída por un usuario anónimo, pero la información a nivel de contenedor no está disponible, es decir, no es posible listar los blobs dentro de un contenedor.
- Container: Acceso anónimo donde los clientes pueden leer la información a nivel del contenedor y la información de los blobs. Por otro lado, no es posible listar los contenedores dentro de un storage account de forma anónima.
- Off: Acceso restringido solamente al propietario de la cuenta.
En el caso de AddBlob cuando recuperamos dicha referencia, dentro de un objeto del tipo CloudBlob, utilizamos la misma para subir el array de bytes que compone el archivo. Si bien en este ejemplo hemos utilizado UploadByteArray, existen varios métodos disponibles para esta función:
En el caso de que necesitáramos realizar la descarga de un blob, el proceso sería bastante similar, al igual que los métodos:
Por otro lado, para el caso de GetBlobs disponemos de un método a nivel del objeto CloudBlobContainer llamado ListBlobs que nos permite listar todos los blobs dentro de un contenedor.
Para finalizar con la clase BlobService podemos ver la forma de eliminar un archivo en el método DeleteBlob donde basta con llamar al método DeleteIfExists una vez recuperada la referencia al blob.
Con esta implementación estamos listos para poder listar, añadir y eliminar blobs en nuestro storage : Arrancamos la aplicación pulsando F5 para visualizar la siguiente imagen:
A medida que vayamos agregando imágenes, podremos ver el nombre del contenedor donde están alojadas las mismas, la Uri independiente de cada una y la su previsualización:
Del mismo modo, si queremos eliminar cualquiera de las imágenes añadidas anteriormente podremos hacerlo de forma independiente seleccionando el link Delete de la fila deseada.
Una vez que estemos listos para dar el paso a producción accedemos al portal de Windows Azure y damos de alta un nuevo servicio de Storage:
Cuando el nuevo storage tenga como status Created, en el apartado de Properties tendremos todos los datos necesarios para realizar la conexión desde nuestra aplicación:
Respecto al código, basta con modificar las siguientes líneas en el constructor de la clase BlobService:
public BlobService()
{
//var account = CloudStorageAccount.DevelopmentStorageAccount;
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
configSetter(ConfigurationManager.ConnectionStrings[configName].ConnectionString));
var account = CloudStorageAccount.FromConfigurationSetting("PicStorage");
_blobClient = new CloudBlobClient(account.BlobEndpoint, account.Credentials);
}
En la primera línea estamos definiendo la forma de obtener la configuración de la Storage Account. En este caso le indicamos que el nombre pasado como parámetro, configName, vamos a ser el nombre de la connection string alojada en el archivo web.config del sitio, utilizando la clase ConfigurationManager.
Por lo tanto, la cuenta será aquella en el archivo de configuración con el nombre PicStorage. Un ejemplo de cadena de conexión podría ser la siguiente:
<connectionStrings>
<add name="PicStorage" connectionString="DefaultEndpointsProtocol=https;AccountName=mypicsstorage;AccountKey=lCjRbkXc4KNwhy5y9kgetKmivdIsPkeZInuwgZ7yubskdQzjlHx5lcLxv5ZlkBfd5gD5ehKaX2VulwJOrG1MBQ=="/>
</connectionStrings>
Si ejecutamos de nuevo la aplicación comprobamos que, al subir de nuevo nuestras imágenes, la URI de las mismas varía, apuntando esta vez al storage en el cloud:
Conclusión
En este artículo hemos visto la forma de trabajar con los blobs del servicio Windows Azure Storage. También hemos comprobado que este servicio no debe estar relacionado necesariamente con una aplicación del cloud sino que puede utilizarse independientemente con otras aplicaciones on-premise, es decir, dentro de nuestras instalaciones.¡Espero que haya sido de utilidad!
Gisela Torres Buitrago
Desarrolladora en tecnologías .NET y Windows Azure MVP