Archivo por meses: abril 2013

Diferencias entre Windows Azure Web sites y Web Role (cloud Service)

Un partner que desarrolla una red social me pregunto la diferencia entre utilizar Web Sites o un Web Role (Cloud Services).

Hay un muy buen post de MSDN que explica las diferencias y los escenarios de cuando utilizar cada uno.

Windows Azure Websites, Cloud Services, and VMs: When to use which?

La siguiente tabla resumen las características principales de cada opción, lo cual puede servirnos para decidir cual es opción que mas nos sirve.

Feature Web Sites Web Roles
Access to services like Caching, Service Bus, Storage, SQL Azure Database Yes Yes
Support for ASP.NET, classic ASP, Node.js, PHP Yes Yes
Shared content and configuration Yes No
Deploy code with GIT, FTP Yes No
Near-instant deployment Yes No
Integrated MySQL-as-a-service support Yes Yes
Multiple deployment environments (production and staging) No Yes
Network isolation No Yes
Remote desktop access to servers No Yes
Ability to run programs with elevated permissions No Yes
Ability to define/execute start-up tasks No Yes
Ability to use unsupported frameworks or libraries No Yes
Support for Windows Azure Connect/ Windows Azure Network No Yes

Links relacionados

Windows Azure Active Directory en la pizarra #windowsazure

En Channel 9 hay una muy Buena explicación de Windows Azure Active Directory. Por el alcance de nombre, he recibido muchas peguntas sobre el tema. La más preocupante fue «puedo dejar de usar Active directory en mi empresa y reemplazarlo por Windows Azure Active directory?»

Este video es muy fácil de seguir y entender las diferencias entre los dos servicios.

waad

Links relacionados

  1. Windows Azure: ASP.NET en Cloud Service utilizando MongoDB
  2. Windows Azure Media Services: Publicar videos para IOS y Windows Phone

Windows Azure: ASP.NET en Cloud Service utilizando MongoDB

Introducción

El mundo de las bases de datos está cambiando vertiginosamente, debido a las nuevas necesidades de información y cantidad de datos que se almacenan actualmente en los sistemas de información. Dentro de este contexto ha aparecido Mongo DB que es una base de datos  no relacional, o cómo le gusta al marketing referirse una base de datos NoSql.

MongoDb es una base de datos orientada a documentos en formato Json que permite esquemas dinámicos. Dentro de las principales características de la base de datos podemos encontrar:

  1.  Indexación por cualquier atributo, es decir búsqueda rápida por cualquier atributo del documento (registro en el mundo relacional)
  2. Soporta Mirror para HA
  3. Escalamiento horizontal utilizando Sharding
  4. Soporte para Map/Reduce, para procesos de procesamiento y agregación flexible de datos

Pueden encontrar más información de MongoDB aquí.

En este Post vamos a hacer una simple aplicación Web (Azure Cloud Service) con C# que utiliza MongoDB para almacenar Notas ingresadas en la Página Web. Además, permite agrupar las notas en libros para mostrar las capacidades de esquema flexible y agrupación de documentos dentro de otros documentos.

Requerimientos del ejemplo

Para poder desarrollar este ejemplo se necesita:

  1. Una Base de datos de MongoDB
  2. Instalar el Driver de C# para Mongo
  3. Windows Azure SDK

Paso a paso de la implementación

Paso 1: Crear una instancia de MongoDB

Para desarrollar el ejercicio vamos a crear una instancia de MongoDB en Azure utilizando el Add-On que ofrece MongoLAB en windows Azure. Para ello seguimos el asistente que se muestra en la siguiente figura, el cual se inicia utilizando el botón NEW, Store y se busca en la lista la opción de MongoDB.

img1

Una vez creada la base de datos de MongoDB, la cual es provista en el modelo DBaaS, podemos ver el panel de control que se muestra en la figura 2.

img2

Para administrar nuestra base de datos Mongo, tenemos que hacer click en el botón Manage de la botonera al pie de página, donde llegaremos a la consola de administración Web de MongoLab, la cual se muestra en la figura 3.

img3

Con esto ya estamos listos para codificar nuestra aplicación.

Paso 2: Crear el proyecto Cloud Service

Para crear nuestro proyecto Cloud Services utilizaremos Visual Studio, creamos un nuevo proyecto del tipo Cloud Services, sin ningún role dentro, como se muestra en la figura 4.

img4

Ahora, vamos a agregar a la solución un proyecto ASP.NET Empty Web Application utilizando el botón derecho sobre la solución, opción Add new proyect. Usamos un Web vacio para que sea más simple poner foco solo en los aspectos de Mongo en el ejemplo.

Cuando tenemos los dos proyectos, debemos agregar el ROL Web al cloud Services. Para ello, utilizando el botón derecho sobre ROLES, utilizamos la opción Add … Web Project in the Solution y seleccionamos nuestro proyecto Web vacío.

Por úlitmo debemos instalar el driver de C# para Mongo. Esto lo hacemos utilizando el módulo Package Manager Console con el comando Install-Package mongocsharpdriver. La figura 5 muestra la consola después de instalarlo.

img5

En este punto tenemos la solución creada y lista para comenzar a programar.

Paso 3: Crear clase de acceso a datos

Para acceder a Mongo vamos a utilizar una clase de acceso a datos, que nos simplifica y aísla el origen de datos de la aplicación. Para esto, creamos una clase llamada DAC. En esta clase agregamos las referencias que necesitamos para Mongo.

using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.IdGenerators;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;

Con Mongo tenemos dos opciones para poder trabajar con los documentos. La primera es al estilo fuertemente tipados, como estamos acostumbrados por ejemplo a trabajar en C# y la otra es utilizar BsonDocumet es decir sin tipos conocidos en tiempo de programación. Aquí, vamos a trabajar primero con objetos con tipos y luego lo haremos son tipos. Obviamente la segunda aproximación da más flexibilidad y potencia para usar las colecciones de documentos de Mongo.

Paso 4: Crear una clase tipo Nota y DAC

Utilizando la primera aproximación de trabajo vamos a crear la siguiente clase

public class DocumentoNota
    {
        public DocumentoNota()
        {
            Date = DateTime.UtcNow;
        }
        private DateTime date;
        [BsonId(IdGenerator = typeof(CombGuidGenerator))]
        public Guid Id { get; set; }
        [BsonElement("Note")]
        public string Text { get; set; }
        [BsonElement("Date")]
        public DateTime Date
        {
            get { return date.ToLocalTime(); }
            set { date = value; }
        }
    }
 

Esta clase representa el documento que vamos a guardar en MongoDb, una nota que tiene un identificador único, una fecha y un texto.

Ahora, vamos a crear la clase Dal que nos ayudará a interactuar MongoDB. El constructor de esta clase recibe la cadena de conexión con Mongo y el nombre de la base de datos.

public class Dal : IDisposable
    {
        private MongoServer mongoServer = null;
        private bool disposed = false;
        private string collectionName = "Notes";
        private string connectionString;
        private string dbName;
        // Default constructor.
        public Dal(string strconnectionString, string strdbName)
        {
            connectionString = strconnectionString;
            dbName = strdbName;
        }
        # region IDisposable
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (mongoServer != null)
                    {
                        this.mongoServer.Disconnect();
                    }
                }
            }
            this.disposed = true;
        }
        # endregion
    }

Paso 5: Conexión y consulta a MongoDB con objetos con tipo

La conexión a mongoDB se hace utilizando las clases MongoServer, MongoClient y MongoDatabase. El método ObtenerDB crea un cliente Mongo que se conecta al servidor y obtiene la referencia conectada a la base de datos.

private MongoDatabase ObtenerDB(string strdbName)
        {
            if (mongoServer == null)
            {
                var client = new MongoClient(connectionString);
                mongoServer = client.GetServer();

            }
            return mongoServer.GetDatabase(strdbName);
        }

Al tener la referencia a la base de datos ya conectada podemos comenzar a hacer consultas. El Método GetAllNotes() se conecta a la base de datos, obtiene la colección de documentos y los entrega en una lista. Aquí debemos notar que estamos usando una colección fuertemente tipada ya que la lista es del tipo <DocumentoNota>.

public List<DocumentoNota> GetAllNotes()
        {
            try
            {
                MongoDatabase database = this.ObtenerDB(this.dbName);
                MongoCollection<DocumentoNota> noteCollection =
                    database.GetCollection<DocumentoNota>(this.collectionName);
                return noteCollection.FindAll().ToList<DocumentoNota>();
            }
            catch (MongoConnectionException)
            {
                return new List<DocumentoNota>();
            }
        }

Para realizar la consulta vamos a utilizar un Web Form que llamaremos defaul.aspx. Para crearlo usamos el botón derecho opción Add New Form. Esto nos entrega una forma vacía a la cual agregamos una GridView llamada GridView1. Luego agregamos un botón que llamamos btActualizar.

Vamos a declarar una propiedad privada del tipo Dal llamada xMongo que utilizaremos en todos los métodos de este Web Form para interactuar con MongoDb. En el método Page_Load() creamos una instancia de xMongo leyendo desde la configuración del Cloud Services la cadena de conexión y el nombre de la base de datos de mongo. Por último, realizamos la consulta en el método ActualizarWebForm() que es el evento que se gatilla cuando se hace click en el botón  btActualizar. El siguiente código muestra lo implementado hasta ahora.

private Dal xMongo;
        private void ActualizarWebForm()
        {
            this.GridView1.DataSource = xMongo.GetAllNotes();
            GridView1.DataBind();
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            if (xMongo == null)
            {
                xMongo =
new Dal(CloudConfigurationManager.GetSetting("mongoDB"), CloudConfigurationManager.GetSetting("dbName"));
            }
        }
        protected void btActualizar_Click(object sender, EventArgs e)
        {
            this.ActualizarWebForm();
        }

Antes de ejecutar nuestra primera consulta debemos ingresar en la configuración los valores de dbName y mongoDB, como se muestra en la siguiente imagen.

img6

Los valores a utilizar los debemos obtener desde el sitio de administración de Mongo LAB. En el panel de control del portal de Azure, en nuestra el ítem de MongoLab creado utilizando el botón de Connection info obtenemos la cadena de conexión de MongoDB. Luego vamos al portal de MongoLab y copiamos el nombre de la base de datos que viene creada por defecto. Para hacer esto, debemos apretar el botón Manage. La siguiente imagen nos muestra el portal de administración.

img7

Si ejecutamos nuestro proyecto, funciona pero no nos trae registros ya que no hemos ingresado dato alguno. Vamos a agregar un método CrearNota() como se muestra a continuación. Aquí recibimos un objeto de tipo Note y lo agregamos a la colección con el método insert.

public void CrearNota(DocumentoNota note)
        {
            MongoCollection<DocumentoNota> collection =this.ObtenerDB(this.dbName).GetCollection<DocumentoNota>(this.collectionName);
            try
            {
                collection.Insert(note, SafeMode.True);
            }
            catch (MongoCommandException ex)
            {
                string msg = ex.Message;
            }
        }

Por último en nuestro formulario Web agregamos un texbox y un botón para ingresar el texto de la nueva nota. Y utilizando el Dal podemos crear nuevas notas.

protected void btNuevaNota_Click(object sender, EventArgs e)
        {
            DocumentoNota valor = new DocumentoNota();
            valor.Date = DateTime.Now;
            valor.Text = tbTexto.Text;
            xMongo.CrearNota(valor);
            tbTexto.Text="";
            this.ActualizarWebForm();
        }

Ahora, ejecutamos nuestro proyecto e ingresamos algunas notas como muestra la siguiente figura.
img8

Paso 6: consulta a MongoDB con objetos sin tipo

Una de las características de Mongo es que puede manejar esquemas dinámicos en los documentos. Para poder aprovechar esta ventaja, no podemos usar objetos con tipos definidos en nuestra aplicación sino que debemos usar un tipo genérico llamado BsonDocument. Ahora, vamos a hacer la misma consulta que hicimos antes para obtener una lista de notas sin tipo, sino que la lista tendrá la lista de documentos en formato de objetos Json. Para esto le agregamos al Dal el siguiente método.

public List<string> GetAllElementosJson( string nombreColeccion)
        {
            var str = new List<string>();

            foreach (var item in this.ObtenerDB().GetCollection<MongoDB.Bson.BsonDocument>(nombreColeccion).FindAll())
            {
                str.Add(item.AsBsonDocument.ToString());
            }
            return str;
        }

Ahora, modificamos nuestro Web Form agregando un checkbox que nos permite seleccionar trabajar con o sin tipo. Actualizamos el método ActualizarWebForm() y podemos ya leer con o sin tipo como se muestra en el código actualizado. Nótese que le entregamos el nombre de la colección de documentos que queremos leer.

        private void ActualizarWebForm()
        {
            if (chbTipo.Checked)
            {
                this.GridView1.DataSource = xMongo.GetAllNotes();
            }
            else
            {
                this.GridView1.DataSource = xMongo.GetAllElementosJson("Notes");
            }
            GridView1.DataBind();
        }

Si desmarcamos el checkbox obtendremos la lista de objetos como muestra la imagen 9.img9

Paso 7: Actualizar Documentos en Mongo a partir de objetos sin tipo

Para crear una nueva nota en la colección, sin utilizar un objeto con tipo utilizamos nuevamente la clase   BsonDocument como muestra el método del Dal crearBsonDocumentNota().

public void crearBsonDocumentNota (string strTexto, string strColeccion)
{
   MongoDB.Bson.BsonDocument documentoX = new MongoDB.Bson.BsonDocument {
   { "_id", Guid.NewGuid() } ,
   { "Date", DateTime.Now.ToLocalTime() },
   { "Note", strTexto }
             };
    var collection =
        this.ObtenerDB(this.dbName).GetCollection<MongoDB.Bson.BsonDocument>(strColeccion);
    try
    {
      collection.Insert(documentoX);
     }
     catch (MongoCommandException ex)
     {
         string msg = ex.Message;
      }
  }

Ahora, actualizamos el método btNuevaNota_Click() para que dependiendo de la selecciona del checkbox agreguemos notas usando tipos o no.

        protected void btNuevaNota_Click(object sender, EventArgs e)
        {
            if (chbTipo.Checked)
            {
                DocumentoNota valor = new DocumentoNota();
                valor.Date = DateTime.Now;
                valor.Text = tbTexto.Text;
                xMongo.CrearNota(valor);
            }
            else
            {
                xMongo.crearBsonDocumentNota(tbTexto.Text,"Notes");
            }
            tbTexto.Text = "";
            this.ActualizarWebForm();
        }

Paso 8: Borra un documento de la colección

En este punto ya podemos crear y ver documentos de una colección, utilizando o no una clase con tipo. Ahora, vemos a borrar las notas de la colección.

public void borrarAllDocumentos(string strCcoleccion)
        {
            MongoCollection < MongoDB.Bson.BsonDocument > notesCollection =
                this.ObtenerDB(this.dbName).GetCollection <MongoDB.Bson.BsonDocument> (strCcoleccion);
            notesCollection.RemoveAll();
        }

Luego en el formulario Web agregamos un botón para borrar todos los documentos e invocamos este método. Al hacerlo vamos a borrar todos los documentos de la colección.

Paso 9: Representar la colección de documentos como un árbol

El paradigma de las bases de datos documentales como mongoDB es tener colecciones de documentos que a su vez pueden contener colecciones de documentos como un atributo. Esto se representa de manera natural en una estructura de árbol más que en una tabla. Las tablas son la representación natural del paradigma tradicional/relacional por excelencia.

Ahora vamos a implementar el mapeo desde una colección de MongoDB hacia un TreeNode para usarlo como fuente de datos del control Web Treeview. Para esto vamos a utilizar recursividad, que debemos recorrer el árbol desde la raíz hasta las hojas de cada rama.

La raíz del árbol se crea en el método getArbol(string strColeccion)  y obtiene la colección de documentos. Aquí se recorre todos los elementos que tiene en este primer nivel la colección de documentos del tipo BsonDocument. Este documento puede contener un arreglo de documentos, un documento con alguna estructura definida o un elemento dentro del documento.

El método obtnerNodo() es quien discrimina el tipo de documento que se está procesando y determina si es una hoja del árbol por lo cual debe ser procesada, un documento o un arreglo de documentos. Los dos últimos casos, que en el Switch del código son MongoDB.Bson.BsonType.Array y MongoDB.Bson.BsonType.Document,  lo que hacen es llamar de manera recursiva al mismo método obtenerNodo() hasta llegar al nivel de la hoja. En ese momento lo que hace el método es agregarlo como un nodo al TreeNode. Ambos métodos se muestran en el siguiente código.

private TreeNode obtnerNodo(MongoDB.Bson.BsonDocument doc, TreeNode hoja)
        {
            foreach (var item in doc.Elements)
            {
                switch (item.Value.BsonType)
                {
                    case MongoDB.Bson.BsonType.Array:
                        var hojaHijaArray = new TreeNode(item.Name);
                        foreach (var documento in item.Value.AsBsonArray)
                        {
                            this.obtnerNodo(documento.AsBsonDocument, hojaHijaArray);
                        }
                        hoja.ChildNodes.Add(hojaHijaArray);
                        break;
                    case MongoDB.Bson.BsonType.Document:
                        var hojaHijaDoc = new TreeNode(item.Name);
                        this.obtnerNodo(item.Value.AsBsonDocument, hojaHijaDoc);
                        hoja.ChildNodes.Add(hojaHijaDoc);
                        break;
                    default:
                        var etiqueta = string.Format("{0}: {1} [{2}]", item.Name, item.Value.RawValue.ToString(), item.Value.BsonType.ToString());
                        hoja.ChildNodes.Add(new TreeNode(etiqueta));
                        break;
                }
            }
            return hoja;
        }
        public TreeNode getArbol(string strColeccion)
        {
            var arbol = new TreeNode(strColeccion);
            int acc = 0;
            foreach (var item in this.ObtenerDB(this.dbName).GetCollection<MongoDB.Bson.BsonDocument>(strColeccion).FindAll())
            {
                arbol.ChildNodes.Add(this.obtnerNodo(item, new TreeNode(acc.ToString())));
                acc += 1;
            }
            return arbol;
        }

El resultado de llamar a getArbol() es un objeto TreeNode que representa la estructura de la colección en un árbol con ramas y hojas, donde las hojas son los valores de las propiedades de los documentos. Para verlo funcionando, ampliamos el método ActualizarWebForm() para que además de mostrar la tabla con valores de la colección, muestre un árbol con la misma.

private void ActualizarWebForm()
        {
            if (chbTipo.Checked)
            {
                this.GridView1.DataSource = xMongo.GetAllNotes();
            }
            else
            {
                this.GridView1.DataSource = xMongo.GetAllElementosJson("Notes");
            }
            tvArbol.Nodes.Clear();
            tvArbol.Nodes.Add(xMongo.getArbol("Notes"));
            GridView1.DataBind();
        }

Ahora, cuando ejecutamos la aplicación de ejemplo y actualizamos vemos tanto los datos en la tabla como en el árbol. Nótese que no estamos usando el método con objetos con tipo definido, ya que la idea es que el mapeo nos sirva para cualquier colección.
img10

Paso 10: Representar una  colección más compleja de documentos como un árbol

Para hacer más ilustrativo el ejemplo, y representar mejor la potencia de los esquemas dinámicos y la posibilidad de contener documentos dentro de documentos, vamos a implementar una segunda colección que sea una lista de las notas ingresadas en la colección Notes.

En la clase Dal vamos a agregar un método que agrupará las notas de la colección Notes en una nueva colección de documentos, el cual tendrá un par de atributos y un elemento llamado “Notas”. Este será una colección de los documentos de las notas ingresadas. Con esto vamos a ver una colección que tiene un documento (raíz) que tiene a su vez en su contenido una colección de documentos. En Dal agregamos el siguiente método.

  public string AgruparNotas(string strColeccionAgrupacion)
        {
            MongoDB.Bson.BsonArray Libro = new MongoDB.Bson.BsonArray();
            var pagina = new MongoDB.Bson.BsonDocument();
            pagina.Add("_id", Guid.NewGuid());
            pagina.Add("cierre", DateTime.Now.ToLocalTime());
            int acc = 0;

            var notas = new MongoDB.Bson.BsonArray();
            foreach (var item in this.ObtenerDB(this.dbName).GetCollection<MongoDB.Bson.BsonDocument>(this.collectionName).FindAll())
            {
                var nota = new MongoDB.Bson.BsonDocument();
                nota.Add("Registro", acc.ToString());
                nota.Add("Nota", item.AsBsonDocument);
                notas.Add(nota);
                acc += 1;
            }
            pagina.Add("Notas", notas);
            Libro.Add(pagina);
            var collection = this.ObtenerDB(this.dbName).GetCollection<MongoDB.Bson.BsonArray>(strColeccionAgrupacion);
            collection.InsertBatch(Libro);

            return Libro[0].AsBsonDocument.ToString();
        }

Ahora, en la interfaz Web volvemos a modificar el método ActualizarWebForm() para que ahora al momento de actualizar el formulario, cree esta nueva colección y la muestre en el Treeview.

private void ActualizarWebForm()
        {
            if (chbTipo.Checked)
            {
                this.GridView1.DataSource = xMongo.GetAllNotes();
            }
            else
            {
                this.GridView1.DataSource = xMongo.GetAllElementosJson("Notes");
            }
            tvArbol.Nodes.Clear();
            xMongo.borrarAllDocumentos("agrupacion");
            xMongo.AgruparNotas("agrupacion");
            tvArbol.Nodes.Add(xMongo.getArbol("agrupacion"));
            GridView1.DataBind();
        }

Una vez hecho esto, el resultado será que el árbol ahora muestra los diferentes niveles. Los documentos de la colección Notes ahora son parte del documento de la colección agrupación como aparece en la imagen 11.img11

El código fuente esta aquí.

Conclusión

En este pequeño artículo podemos ver cómo utilizar MongoDb de manera muy básica y comenzar a comprender el paradigma de las bases de datos documentales. Además, vemos como MongoDb está ya disponible en el modelo DBaaS en Windows Azure y como una aplicación Cloud Services del tipo Web puede utilizarla.

Links Relacionados

Windows Azure Web sites #wapucolombia

Esta es la  presentación de Web sites del WAPU en Colombia, esta presentación es parte del
Azure tranning Kit. Este es el kit de entrenamiento de  Azure el cual es muy recomnedable para  aprender a utilizar la Nube.

Links relacionados

Windows Azure Media Services: Publicar videos para IOS y Windows Phone #azure

Introducción

Para el evento llamado Windows Azure Partner Academy, presenté como utilizar Windows Azure media Services de manera programática utilizando el SDK. La idea de la demostración es poder automatizar el flujo de ingestión, codificación y publicación de contenido en Media Services. En este post, se explica el código utilizado en esa demostración.
El propósito del ejemplo es tomar un video desde el disco local, subirlo a windows Azure Media Services, codificarlo primero en H264 y luego en HLS para que pueda ser visto por los sistemas IOS como es un Iphone, Ipad, etc. Todo esto de manera automática.

Pre requisitos

Para poder implementar un cliente que se conecte a Media Services y utilice las API para la ingestión, codificación, empaquetamiento y publicación del contenido se requieren los componentes y configuración en tú equipo.

  1. Un servicio Media Services creado
  2. Las llaves de acceso al servicio
  3. NET Framework 4.5 or .NET Framework 4.
  4. Visual Studio 2012 or Visual Studio 2010 SP1
  5. Windows Azure SDK for .NET
  6. Windows Azure Media Services SDK for .NET
  7. WCF Data Services 5.0 for OData V3 libraries

Paso a paso del proyecto

Paso 1: Crear el proyecto de consola y configuración

Para comenzar, se crea un proyecto de consola del tipo C# en Visual Studio. Para que este programa pueda acceder a Media Services se debe utilizar el Windows Azure media Services SDK for .NET. Este se puede obtener de manera muy simple utilizando NugetPackage como se ve en la siguiente figura.

Con esta referencia ya podemos hacer código que se comunique con Media Services.

Paso 2: Obtener Contexto en Media Services

Ahora, debemos obtener las Manage Keys del servicio. Para ello vamos al portal, entramos al servicio y las obtenemos utilizando el botón del menú de la barra inferior, como se muestra en la siguiente figura.

La idea es que utilicemos el Account Name y Primary Media Service Access Key para obtener el contexto del servicio y así poder comenzar a trabajar con media Services.


        static CloudMediaContext ObtenerContexto()
        {
            return new CloudMediaContext(_accountName, _accountKey);
        }
  

Para subir el archivo, vamos a utilizar el método Subir1ArchivoCrear que recibe como argumentos la opción de creación del Asset y el nombre del archivo que contiene el video. Las opciones de creación definen por ejemplo si el Asset estará cifrado en el storage. Para efectos de nuestro ejemplo usamos la opción por None. El método lo que hace es crear un nombre para identificar el Asset en Media Services, crearlo con ese nombre vacío y subir el video a media services.

static IAsset Subir1ArchivoCrear(AssetCreationOptions assetCreationOptions, string singleFilePath)
        {
            //1. Crear el nomrbe del Asset en Media Services
            var assetName = "TMP_UploadSingleFile_" + DateTime.UtcNow.ToString();
            var asset = CreateEmptyAsset(assetName, assetCreationOptions);
            var fileName = Path.GetFileName(singleFilePath);
            //2. Crear un Asset vacio
            var assetFile = asset.AssetFiles.Create(fileName);
            _PrefijoNombreAsset = assetFile.Name;
            Console.WriteLine("Creado el Archivo {0}", assetFile.Name);
            //3. Crear la Politica
            var accessPolicy = _contexto.AccessPolicies.Create(assetName, TimeSpan.FromDays(30),
                                                                AccessPermissions.Write | AccessPermissions.List);
            //4. Crear el Locator
            var locator = _contexto.Locators.CreateLocator(LocatorType.Sas, asset, accessPolicy);
            Console.WriteLine("Upload {0}", assetFile.Name);
            //5. Se crea un blob transfer para subir el video a Media Services
            var blobTransferClient = new BlobTransferClient();
            blobTransferClient.NumberOfConcurrentTransfers = 20;
            blobTransferClient.ParallelTransferThreadCount = 20;
            //6. se define el evento que muestra el avance del upload
            blobTransferClient.TransferProgressChanged += blobTransferClient_TransferProgressChanged;
            //7. Se genera un task list asincrono para subir el archivo
            var uploadTasks = new List<Task>();
            uploadTasks.Add(assetFile.UploadAsync(singleFilePath, blobTransferClient, locator, CancellationToken.None));
            //8. Se espera que termine de subir
            Task.WaitAll(uploadTasks.ToArray());
            Console.WriteLine("Done uploading of {0}", assetFile.Name);
            locator.Delete();
            accessPolicy.Delete();
            return asset;
        }

Paso 3: Crear el Job de codificación H264

El primer trabajo de codificación es al formato H264. Los Jobs son trabajos de media Services que están compuestos por tareas, pudiendo un Job contener varias tareas. En este caso, por simpleza del ejemplo, vamos a crear un Job con solo una tarea.
En la tarea se especifica el tipo de codificación que se utilizará y la codificación del Asset de salida. La codificación utilizada es estándar y se especifica como «H264 Smooth Streaming 720p».
Luego de ejecutar el JOB, en vez de utilizar progressJobTask.Wait() que solo muestra cuando el JOB termina, se utiliza código para mostrar avance en porcentaje, como en el portal. Por último, se retorna la referencia al JOB una vez finalizado.

 static IJob CrearTrabajoCod_H264Smooth(IAsset asset)
        {
            //1. Crear un Nuevo JOB
            IJob job = _contexto.Jobs.Create("Primer JOB de encode: H264 Smooth Streaming 720p");
            // Get a media processor reference, and pass to it the name of the
            // processor to use for the specific task.
            IMediaProcessor processor = GetLatestMediaProcessorByName("Windows Azure Media Encoder");
            //2. Se crea una tarea con los detalles de la codificación.
            //en este caso  "H264 Adaptive Bitrate MP4 Set 720p"
            ITask task = job.Tasks.AddNew("task: H264 Smooth Streaming 720p",processor,
                "H264 Smooth Streaming 720p", TaskOptions.ProtectedConfiguration);
            //3. Especifica el Asset a codificar
            task.InputAssets.Add(asset);
            //4. Crea y especifica el Asset de salida, sin cifrado
            string nombreContenido = _PrefijoNombreAsset + "_H264";
            task.OutputAssets.AddNew(nombreContenido,AssetCreationOptions.None);
            //5. El manejador de evento que muestra el avance del job
            job.StateChanged += new EventHandler<JobStateChangedEventArgs>(StateChanged);
            //6. Se ejecuta el JOB
            job.Submit();
            //7. Validar el estado del Job
            Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None);
           //8. en vez de utilizar  progressJobTask.Wait(); que solo muestra cuando el JOB termina
            //se utiliza el siguiente codigo para mostrar avance en porcentaje, como en el portal
            double avance = 0;
            while ((job.State != JobState.Finished) && (job.State != JobState.Canceled) && (job.State != JobState.Error))
            {
                if (job.State==JobState.Processing)
                {
                    if (avance != (job.Tasks[0].Progress / 100))
                    {
                        avance = job.Tasks[0].Progress / 100;
                        Console.WriteLine("Percent complete: {0}", avance.ToString("#0.##%"));
                    }
                }
                Thread.Sleep(TimeSpan.FromSeconds(5));
            }
            // 9. se regresa el la referencia al JOB
            job = GetJob(job.Id);
            return job;
        }

Una vez codificado el video tendremos 2 Assets en media Services. El video original y el codificado a H264. Ya no necesitamos el original, por lo cual lo borramos utilizando el siguiente método.

static void BorrarAsset(IAsset asset)
{
    // delete the asset
    asset.Delete();
    // Verify asset deletion
     if (GetAsset(asset.Id) == null)
         Console.WriteLine("Deleted the Asset");
}

Paso 4: Crear el Job de codificación HLS

Como el objetivo es poder mostrar el video en IOs también, entonces debemos codificarlo en HLS para que así los Iphone, Ipad, etc puedan verlo. Para ellos vamos a crear un JOB, con una tarea que haga la codificación. Ahora, para especificar la codificación, a diferencia a lo hecho en el paso anterior con el formato H264, vamos a especificar la codificación con el siguiente archivo XML.

<?xml version="1.0" encoding="utf-8" ?>
<taskDefinition xmlns="http://schemas.microsoft.com/iis/media/v4/TM/TaskDefinition#">
  <name>Smooth Streams to Apple HTTP Live Streams</name>
  <id>A72D7A5D-3022-45f2-89B4-1DDC5457C111</id>
  <description xml:lang="en">Converts on-demand Smooth Streams encoded with H.264 (AVC) video and AAC-LC audio codecs to Apple HTTP Live Streams (MPEG-2 TS) and creates an Apple HTTP Live Streaming playlist (.m3u8) file for the converted presentation.</description>
  <inputDirectory></inputDirectory>
  <outputFolder>TS_Out</outputFolder>
  <properties namespace="http://schemas.microsoft.com/iis/media/AppleHTTP#" prefix="hls">
    <property name="maxbitrate" required="true" value="1600000" helpText="The maximum bit rate, in bits per second (bps), to be converted to MPEG-2 TS. On-demand Smooth Streams at or below this value are converted to MPEG-2 TS segments. Smooth Streams above this value are not converted. Most Apple devices can play media encoded at bit rates up to 1,600 Kbps."/>
    <property name="manifest" required="false" value="" helpText="The file name to use for the converted Apple HTTP Live Streaming playlist file (a file with an .m3u8 file name extension). If no value is specified, the following default value is used: &lt;ISM_file_name&gt;-m3u8-aapl.m3u8"/>
    <property name="segment" required="false" value="10" helpText="The duration of each MPEG-2 TS segment, in seconds. 10 seconds is the Apple-recommended setting for most Apple mobile digital devices."/>
    <property name="log"  required="false" value="" helpText="The file name to use for a log file (with a .log file name extension) that records the conversion activity. If you specify a log file name, the file is stored in the task output folder." />
    <property name="encrypt"  required="false" value="false" helpText="Enables encryption of MPEG-2 TS segments by using the Advanced Encryption Standard (AES) with a 128-bit key (AES-128)." />
    <property name="pid"  required="false" value="" helpText="The program ID of the MPEG-2 TS presentation. Different encodings of MPEG-2 TS streams in the same presentation use the same program ID so that clients can easily switch between bit rates." />
    <property name="codecs"  required="false" value="false" helpText="Enables codec format identifiers, as defined by RFC 4281, to be included in the Apple HTTP Live Streaming playlist (.m3u8) file." />
    <property name="backwardcompatible"  required="false" value="false" helpText="Enables playback of the MPEG-2 TS presentation on devices that use the Apple iOS 3.0 mobile operating system." />
    <property name="allowcaching"  required="false" value="true" helpText="Enables the MPEG-2 TS segments to be cached on Apple devices for later playback." />
    <property name="passphrase"  required="false" value="" helpText="A passphrase that is used to generate the content key identifier." />
    <property name="key"  required="false" value="" helpText="The hexadecimal representation of the 16-octet content key value that is used for encryption." />
    <property name="keyuri"  required="false" value="" helpText="An alternate URI to be used by clients for downloading the key file. If no value is specified, it is assumed that the Live Smooth Streaming publishing point provides the key file." />
    <property name="overwrite"  required="false" value="true" helpText="Enables existing files in the output folder to be overwritten if converted output files have identical file names." />
  </properties>
  <taskCode>
    <type>Microsoft.Web.Media.TransformManager.SmoothToHLS.SmoothToHLSTask, Microsoft.Web.Media.TransformManager.SmoothToHLS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</type>
  </taskCode>
</taskDefinition>

Este archivo le indica al encoder de media Services que va a recibir un video H264 (AVC) video y AAC-LC audio y que lo convierta a Apple HTTP Live Streams (MPEG-2 TS).
El método CreateSmoothToHlsJob utiliza el archivo de configuración para crear la tarea de codificación en base a esa configuración.

public static IJob CreateSmoothToHlsJob(string configFilePath, IAsset asset)
{
    // 1. Usamos la configuración de codificacón desde un Archivo XML
    string configuration = File.ReadAllText(Path.GetFullPath(configFilePath
        + @"\MediaPackager_SmoothToHLS.xml"));

    //2. Valida que el Asset contenga un solo archivo ISM
    var ismAssetFiles = asset.AssetFiles.ToList().
                Where(f => f.Name.EndsWith(".ism", StringComparison.OrdinalIgnoreCase)).ToArray();
    if (ismAssetFiles.Count() != 1)
        throw new ArgumentException("The asset should have only one, .ism file");
    ismAssetFiles.First().IsPrimary = true;
    ismAssetFiles.First().Update();
    // 3. Creal el JOB
    IJob job = _contexto.Jobs.Create("My Smooth Streams to Apple HLS job");
    IMediaProcessor processor = GetLatestMediaProcessorByName("Windows Azure Media Packager");
    // 4. Crea la tarea con los detalles de codificación del archivo XML
    ITask task = job.Tasks.AddNew("My Smooth to HLS Task",
        processor,
        configuration,TaskOptions.ProtectedConfiguration);
    // 5. Define el Asset de entrada
    task.InputAssets.Add(asset);
    // 6. Crea el Asset de Salida con el sufijo HLS.
    string nombreContenido = _PrefijoNombreAsset + "_HLS";
    task.OutputAssets.AddNew(nombreContenido, AssetCreationOptions.None);
    //7. define el manejador del evento
    job.StateChanged += new EventHandler<JobStateChangedEventArgs>(StateChanged);
    //8. Lanza el JOB
    job.Submit();
    //9. Revisa el estado de ejecución del JOB
    Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None);
    //10. en vez de utilizar  progressJobTask.Wait(); que solo muestra cuando el JOB termina
    //se utiliza el siguiente codigo para mostrar avance en porcentaje, como en el portal
    double avance = 0;
    while ((job.State != JobState.Finished) && (job.State != JobState.Canceled) && (job.State != JobState.Error))
    {

        if (job.State == JobState.Processing)
        {
            if (avance != (job.Tasks[0].Progress / 100))
            {
                avance = job.Tasks[0].Progress / 100;
                Console.WriteLine("Percent complete: {0}", avance.ToString("#0.##%"));
            }
        }

        Thread.Sleep(TimeSpan.FromSeconds(5));
    }
    //11. regresa la referencia al JOB terminado
    return job;
}

Paso 5: Publicar los videos ya codificados

Para esto se debe obtener la referencia al manifiesto del Asset, el archivo de extensión ISM. Al publicar debemos especificar la política de uso de lo que estamos publicando, en este caso le vamos a decir que se publica solo lectura por 30 días. Inmediatamente después debemos crear un origen donde localizar nuestro manifiesto y asignarle una URL. Esta es la que utilizaran los clientes para acceder al contenido. Ahora, en el caso de ser HLS debemos agregar «(format=m3u8-aapl)» a la URL.

public static ILocator GetStreamingOriginLocator(string targetAssetID, MediaContentType contentType)
{
    // 1. Obtener la referencia al Asset que se quiere publicar
    IAsset assetToStream = GetAsset(targetAssetID);
    //2. Obtener la referencia del manifiesto
    var theManifest =
                        from f in assetToStream.AssetFiles
                        where f.Name.EndsWith(".ism")
                        select f;

    // 3. crear una referencia al manifiesto
    IAssetFile manifestFile = theManifest.First();
    //3. Crear la politica de acceso de 30 dias solo lectura.
    IAccessPolicy policy = _contexto.AccessPolicies.Create("Streaming policy",
        TimeSpan.FromDays(30),
        AccessPermissions.Read);
    //4.Crear un Locator
    ILocator originLocator = _contexto.Locators.CreateLocator(LocatorType.OnDemandOrigin, assetToStream,
        policy,
        DateTime.UtcNow.AddMinutes(-5));
    Console.WriteLine("Streaming asset base path on origin: ");
    Console.WriteLine(originLocator.Path);
    Console.WriteLine();
    //5. Crear una URL completa para el manifiesto
    string urlForClientStreaming = originLocator.Path + manifestFile.Name + "/manifest";
    //5.1 En el caso de ser HLS se debe agregar al final de la URL  "(format=m3u8-aapl)"
    if (contentType == MediaContentType.HLS)
        urlForClientStreaming = String.Format("{0}{1}", urlForClientStreaming, "(format=m3u8-aapl)");

    Console.WriteLine("URL to manifest for client streaming: ");
    Console.WriteLine(urlForClientStreaming);
    Console.WriteLine();
    Console.WriteLine("Origin locator Id: " + originLocator.Id);
    Console.WriteLine("Access policy Id: " + policy.Id);
    Console.WriteLine("Streaming asset Id: " + assetToStream.Id);
    // 6. regresa el origen
    return originLocator;
}

Paso 6: Juntar todo en una demostración consistente

Ahora que vimos cada uno de los métodos principales que necesitamos para hacer la ingestión, codificación y publicación del contenido vamos a ver el cómo se articulan todos estos métodos en una secuencia coherente. El siguiente código lleva a cabo la secuencia completa.

static void Demo1()
{
    _accountName = "xxxxxx";
    _accountKey = "xxxxxx=";
    _contexto = ObtenerContexto();
    //Video
    string fileName = @"K:\video\interview2.wmv";
    Console.WriteLine("\n 1.Subir el Archivo\n");
    IAsset VideoOriginal = Subir1ArchivoCrear(AssetCreationOptions.None, fileName);
    //Crear el JOB para H264
    Console.WriteLine("\n 2.Crear el JOB para H264\n");
    IJob myJob = CrearTrabajoCod_H264Smooth(VideoOriginal);
    // Paso 4: Borrar el Video Origial
    Console.WriteLine("\n 3.Borrar el Video Origial\n");
    BorrarAsset(VideoOriginal);
    // Paso 4: Crear el JOB para HLS
    Console.WriteLine("\n  4.Crear el JOB para HLS\n");
    IJob myHLSjob = CreateSmoothToHlsJob(_configFile, myJob.Tasks[0].OutputAssets[0]);
    //Paso 5: Publicar H264
    Console.WriteLine("\n 5.Publicar H264\n");
    GetStreamingOriginLocator(myJob.Tasks[0].OutputAssets[0].Id, MediaContentType.SmoothStreaming);
    //Publicar HLS
    Console.WriteLine("\n 6.Publicar HLS\n");
    GetStreamingOriginLocator(myHLSjob.Tasks[0].OutputAssets[0].Id, MediaContentType.HLS);
    //Lista de Videos
    ListAssets();
    //Salir
    Console.WriteLine("\nEspera click para borrar todo.....[s/n]");
    string siono=Console.ReadLine();
    if (siono == "s")
    {
        BorrarTodo();
    }
    Console.WriteLine("\nFin.....");
    Console.ReadLine();
}

Al ejecutar el proceso obtenemos las siguientes salidas.

En el paso 1 podemos ver como se sube el archivo original al contenido de Media Services.

Paso1

En el paso 2, podemos ver el avance del primer JOB(H.264)

Paso2

En el paso 3, se muestra cuando se borra el archivo original.

paso3

En el paso 4, vemos el avance en la codificación HLS

paso4

por último, podemos ver la publicación de ambos videos en Media Services.

paso5y6

EL código fuente del ejemplo desarrollado pueden descargarlo desde aquí.

Conclusión

Este es un simple ejemplo de cómo podemos utilizar Windows Azure Media Services de manera programática para poder llevar a cabo un proceso de publicación de videos para dos plataformas distintas de manera automática.

Links relacionados

ITCamps Azure Santiago #azure

Durante abril voy a apoyar a Alex en la ejecución de los ITCAMP, actividades de entrenamiento técnico para profesionales de IT con foco en el uso de Windows Azure.

Esta es la invitación al evento.

——————————————————————————————————————————————

itcamps[1]

La nube, la alternativa para extender nuestro Datacenter.

Te invitamos a participar de nuestros próximos laboratorios de 5 horas basados en escenarios prácticos aplicables a cualquier tipo de empresa. Esta vez utilizaremos la nube de Microsoft, en su visión de Infraestructura como Servicios para crear máquinas virtuales que complementaran nuestro Datacenter.

En estas sesiones aprenderá como desplegar un SharePoint en la nube de Microsoft, una réplica de su Active Directory y la publicación de una aplicación Web. Todo esto mediante el panel de administración de Windows Azure o mediante PowerShell.

Estos laboratorios se repiten en 4 sesiones, regístrate en la que prefieras escogiendo de las siguientes alternativas:

Registro

——————————————————————————————————————————————

inks relacionados