Importando el calendario de nuestro evento favorito

El próximo 27 y 28 de noviembre estaré un año más en el Codemotion, un evento que se celebra en Madrid reúne comunidades de todo tipo y del que hemos hablado en otros artículos de años anteriores.

En ellas desarrolladores de .NET, Java y la JVM, Ruby, Python, JavaScript, Objective-C, Swift, PHP y otros, se reunen para enseñarnos lo mejor de todos los mundos. Yo ya tengo mi entrada, si no tienes la tuya ya estás tardando

Sin embargo hay algo que sigo sin entender. Al igual que en otros eventos, tenemos disponible la agenda completa en la web, y probablemente el primer día nos entregarán una copia EN PAPEL que acabará en una papelera varios días después del evento, si no lo perdemos antes.

No sería mucho más conveniente poder tener el calendario oficial del evento en un formato que lo pudiera entender nuestro Google Calendar, Outlook, iCal o cliente que utilicemos, y poder usar nuestro móvil para orientarnos por el evento? Pues bien, eso es lo que he hecho, y es lo que veremos en este artículo:

Primer paso, obtener los datos

Para obtener los datos originales tenemos muchas maneras. Una de ellas es utilizar un servicio como Kimono que nos permite crear una API REST a partir de una página ya existente, lo cual nos puede resultar muy útil para extraer información.

Otra opción consiste en investigar un cómo, y qué datos carga la página. En el caso de Codemotion, la agenda se carga de manera asíncrona, así que tras investigar el panel de red de Chrome podemos ver un fichero en formato JSON que contiene, para nuestro disfrute, la agenda completa a nuestra disposición, así que, misión cumplida.

Segundo paso, comprender el formato de salida

Para poder generar un fichero que sea compatible con los diferentes clientes de email, una de las opciones es utilizar el formato icalendar, que define un evento de la siguiente manera:

BEGIN:VEVENT
SUMMARY:Document like a hero using Asciidoctor
DTSTART:20151127T160000Z
DTEND:20151127T164500Z
DESCRIPTION:In 2013, the Spring team decided to migrate the Starting Guides on spring.io ...
END:VEVENT

Analicemos los diferentes componentes, dejando de lado las cabeceras begin y end del evento:

  • Summary: Título que se mostrará en la cita del calendario
  • Dtstart: Fecha y hora de inicio de la cita en la zona horaria UTC, en el siguiente formato: Año Mes Día (T) Hora Minutos Segundos (Z)
  • Dtend: Fecha y hora de fin de la cita, en el mismo formato anteriormente comentado
  • Description: Detalles del evento

Si hemos utilizado el calendario de manera corporativa echaremos de menos temas como invitados, alarmas, etc, aunque todos ellos forman parte del estándar y podemos utilizarlos sin problemas. Para reunir varios eventos en un fichero iCalendar hemos de establecer unas cabeceras y un pie de página de la siguiente manera:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
...
END:VCALENDAR

En este código, definimos nuestro fichero iCalendar, la versión del mismo, y el identificador del generador (que en este caso lo he tomado directamente del ejemplo de la wikipedia). Cerramos el fichero con el marcador End.

Con esta información, ya podemos generar nuestro calendario.

Tercer paso, generar el calendario

Para este ejemplo, como utilizaba un fichero en formato json, me pareció mucho más sencillo utilizar JavaScript y Node.js para generar el fichero ics.


var fs = require('fs');
var obj = JSON.parse(fs.readFileSync('data.json', 'utf8'));
var result = "";
var writer = {
log : function(params){
result += params + "\r\n";
console.log(params);
},
end: function(){
fs.writeFile('codemotion.ics',result);
}
}
function pad(num) {
var zero = 2 – num.toString().length + 1;
return Array(+(zero > 0 && zero)).join("0") + num;
}
//var writer = console;
writer.log("BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//hacksw/handcal//NONSGML v1.0//EN")
obj.days.forEach(function(day) {
day.tracks.forEach(function(track) {
track.slots.forEach(function(slot) {
if(!slot.contents){
return;
}
if(!slot.contents.title){
return;
}
writer.log("BEGIN:VEVENT");
if(slot.contents.type == "BREAK"){
writer.log("SUMMARY:" + slot.contents.title);
} else {
writer.log("SUMMARY:" + "[" + track.name + "] " + slot.contents.title);
}
var dayName = day.name.substring(0,2);
writer.log("DTSTART:201511" + dayName + "T" + pad(parseInt(slot.start.substring(0,2)) – 1) + slot.start.substring(3,5) + "00Z");
writer.log("DTEND:201511" + dayName + "T" + pad(parseInt(slot.end.substring(0,2)) – 1) + slot.end.substring(3,5) + "00Z");
var description = "";
if(slot.contents.description){
description += "DESCRIPTION:" + slot.contents.description.replace(/(\r\n|\n|\r)/gm,"\\n");
}
if(slot.contents.authors){
for(var i = 0; i < slot.contents.authors.length; i++){
var author = slot.contents.authors[i];
description += "\\n\\nAutor: " + author.name + " (" + author.uuid + ") \\n:" + author.description.replace(/(\r\n|\n|\r)/gm,"");
}
}
if(slot.contents.description){
writer.log(description);
}
writer.log("END:VEVENT");
}, this);
}, this);
}, this);
writer.log("END:VCALENDAR");
if(writer.end){
writer.end();
}

view raw

gistfile1.js

hosted with ❤ by GitHub

Una de las cosas que nos aporta JavaScript, es que, al ser dinámicamente tipado, podemos abstraer la consola en un objeto creado por nosotros, y asignarlo o no a la consola en función de nuestra necesidad.

Como detalle a tener en cuenta está el uso de saltos de línea, ya que hemos de utilizar \n, de otra manera, se generará una nueva línea en el fichero de texto resultante y tendremos un error al importarlo.

Además, aunque parezca obvio, podemos tener más de un evento en un mismo fichero ics.

Cuarto paso, probarlo

Por último, y no por ello menos importante, es necesario confirmar que nuestro calendario se ha generado correctamente, para lo que podemos utilizar servicios como iCalendar validator. Finalmente recomiendo utilizar alguna aplicación de escritorio (como iCal en Mac) para hacer un par de importaciones y comprobar que todos los eventos se generan correctamente.

Conclusiones

Podemos generar un calendario utilizando el formato ics y teniendo una fuente de origen de datos. Si nuestra fuente está en formato json, podemos utilizar JavaScript de manera sencilla para movernos por el mismo, y generar nuestro fichero de resultado, que, por cierto, tienes disponible públicamente en Github.

Yo ya tengo mi calendario, y tú?

Enlaces adicionales:

Vídeo: ASP.NET vNext en 10 minutos

En este vídeo resumo tres de las características de las que hemos hablado en Hangouts y en eventos como los de MSCoders, que son el Core CLR, la inyección de dependencias y la configuración a través de ficheros JSON.

Para más artículos relacionados, puedes encontrar información con la etiqueta vnext de este blog.

Tipos de respuesta y plantillas en NancyFx

En los artículos anteriores de la serie, hacíamos un breve repaso a cómo crear módulos y rutas con NancyFx, en este veremos cómo responder a las peticiones usando texto plano, JSON o ficheros, así como usar un viewengine que nos permita, a través de plantillas, usar elementos generados por el servidor en documentos HTML.

Presentando al Content-Type

El Content-type es un mensaje que forma parte de los encabezados HTTP y especifica el tipo de recurso que soliticamos y que se devuelve en una respuesta para que el navegador lo pueda procesar. De esa manera, un content-type de tipo text/html se procesará en el cliente como un fichero HTML a renderizar, el tipo application/octet-stream se referirá habitualmente a un fichero binario, y así para ficheros de audio, vídeo u otros.

Nancy permite especificar el tipo de contenido que estamos devolviendo, de esta manera podemos rescatar el ejemplo anterior donde especificábamos el código de retorno, y modificarlo para que devuelva un contenido con un tipo mime específico:

public class ResponseTypes : NancyModule
{
	public ResponseTypes ()
	{
		Get["/responses/text"] = _ => {
			return ((Response)"<h1>Texto plano</h1>\n").
				WithContentType("text/plain");
		};

		Get["/responses/html"] = _ => {
			return ((Response)"<h1>Texto HTML</h1>\n").
				WithContentType("text/html");
		};

		Get["/responses/binary"] = _ => {
			return ((Response)"<h1>Texto en un fichero</h1>\n").
				WithContentType("application/octet-stream");
		};
	}
}

En este caso, la primera respuesta, aunque espefiquemos código HTML, mostrará el texto plano, en la segunda hemos especificado que se trata de html, con lo cual la respuesta se renderizará por pantalla. En la tercera respuesta, hemos establecido el tipo a binario, con lo cual la acción a realizar será la de descargar el contenido a un fichero en disco.

JSON

Javascript Object Notation, o JSON, es un viejo conocido de este blog, ya que lo hemos usado para acceder a Azure Mobile Services, a Twitter, y a otros. Es una representación ligera de datos que tiene un formato similar a este:

{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567"
        }
    ]
}

Una de las cosas que nos permite NancyFx, es codificar en formato Json un objeto (o un conjunto de ellos) para devolverlos al cliente. Veamos un ejemplo:

public class Person
{
	public string Name {
		get;
		set;
	}
	public string Surname {
		get;
		set;
	}
}public class JsonResponse : NancyModule
{
	public JsonResponse ()
	{
		Get["/json/single"] = _ => Response.AsJson(
			new Person()
			{
				Name = "Roberto",
				Surname = "Luis"
			}
		);
	}
}

En este ejemplo, creamos un objeto, y usando Response.AsJson codificamos su contenido en formato Json y lo devolvemos. La respuesta, si la viésemos en un navegador, tendría este aspecto:

{
    "Name": "Roberto",
    "Surname": "Luis"
}

Si examinamos cuidadosamente la cabecera de la respuesta (usando curl por ejemplo), veremos además el valor del campo Content-Type:

Content-Type: application/json; charset=utf8

Lo que ha ocurrido es que Nancy automáticamente ha gestionado las cabeceras por nosotros. Contamos además con métodos como AsXml, AsImage o AsFile para devolver nuestros objetos en otros formatos.

ViewEngines

Un view engine, o motor de vistas, es un componente que nos permite combinar plantillas HTML con llamadas al servidor, evitandonos el engorro de trabajar directamente con html dentro del código, y posiblitando una separación de responsabilidades. Nancy incluye uno por defecto, llamado «Super Simple View Engine», que permite tener una plantilla de este estilo:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"> <title>External template</title>
  </head>
  <body>
    <h1>Hello, @Model.Name!</h1>
    <ul>
    @Each.Model.Children
    <li>@Current</li>
      @EndEach
    </ul>
  </body>
</html>

Los campos @Model.Name y @Model.Children, nos pueden recordar a Razor, el motor de vistas de ASP.net MVC3, mientras el uso de @Each.Model nos pueden recordar a las plantillas erb de ruby. Estos elementos nos permiten agregar datos de manera dinámica a un documento HTML.

La variable @Model, a la que hemos accedido desde la vista, se define desde el controlador, y puede ser un objeto perteneciente a una clase, o como se muestra en el ejemplo, un objeto anónimo que contenga los datos que necesitamos:

public class ViewTemplates : Nancy.NancyModule
{
	public ViewTemplates ()
	{
		Get ["/ViewEngines/WithData"] = _ => {
			var model = new { 
				Name = "John Smith", 
				Children = new []{"Mike", "Bob", "James"}
			};
			return View["ViewEngines/TemplateWithData.html", model];
		};
	}
}

Finalmente, se devuelve la vista usando la ruta relativa de la misma, y el objeto que servirá de modelo.

Resumen

En este artículo se ha visto cómo podemos personalizar el tipo de respuesta, ya sea texto, binario, o JSON, así como usar un motor de vistas para separar la vista del controlador. Con este artículo terminamos esta serie de introducción a NancyFx, si deseas consultar más información, aquí están los artículos anteriores de la serie:

Enlaces adicionales

Azure Mobile Services bajo el capó: Insertando elementos

Azure Mobile Services nos proporciona servicios de backend en la nube para Windows Phone, Windows 8, iOS, Android y plataformas basadas en web. Permite además agregar seguridad, autenticación y notificaciones de manera sencilla y rápida. Pero, cómo funciona realmente?

Para averiguarlo, podemos recurrir a la documentación que tenemos en MSDN, aunque también podemos ver directamente el código fuente.

La API que se nos ofrece es open source, con lo cual podemos no solamente ver el código fuente sino además poder realizar contribuciones al mismo.

En este artículo quiero hacer un breve recorrido por la API, para una operación muy concreta, insertar elementos. En otros artículos de esta serie veremos cómo hacer consultas, o qué pasa del lado de servidor.

Para este artículo usaremos los siguientes elementos:

Los ejemplos los veremos con el SDK de Windows Phone 8, pero podemos ver que existe un código similar para el resto de plataformas compatibles.

De un vistazo

La API de Azure Mobile Services está compuesto por objetos que nos ayudan a:

  • Convertir nuestros objetos a formato JSON para su envío y hacer la operación contraria a la recepción (Serialización)
  • Realizar solicitudes al servicio que hemos creado en Azure mediante comandos HTTP usando una arquitectura REST
  • Gestionar autentificación y seguridad para las mismas.

El código fuente del SDK para Windows Phone 8 contiene varios elementos

  • Tests que aseguran su integridad (a nivel de core como a nivel de cada cliente en particular).
  • Código manejado (es decir, código en .NET tradicional).
  • Código nativo, dentro de los espacios de nombres de WinMD (Recordemos que Windows Phone 8 tiene soporte para las API nativas de WinRT).

Para poder trabajar con Mobile Services necesitamos en primer lugar una referencia a la tabla:

private MobileServiceCollection<TodoItem, TodoItem> items;
private IMobileServiceTable<TodoItem> todoTable = 
    App.MobileService.GetTable<TodoItem>();

Este método está situado en la clase MobileServiceClient que contiene otros que veremos a lo largo del artículo:

Este método está situado en la siguiente ruta:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.Managed.cs

El código es el siguiente:

public IMobileServiceTable GetTable()
{
    SerializableType type = SerializableType.Get(typeof(T));
    return new MobileServiceTable(type.TableName, this);
}

En primer lugar obtiene un tipo serializable específico, que es el que se usará para poder transformar el objeto en una tabla en el servidor. Luego generará una tabla de tipo MobileServiceTable con el nombre creado a partir de este tipo. Hasta aquí solamente hemos hecho trabajo en local, es decir, no ha habido conexión con el servicio.

Veamos qué pasa al insertar un elemento en la tabla:

await todoTable.InsertAsync(todoItem);

El código para esta función se encuentra en la siguiente ruta:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceTable.Generic.cs

En este código lo que vemos es lo siguiente:,

  • No podemos insertar elementos nulos,
  • Se obtiene un JObject (un objeto serializado en formato JSON) a partir de MobileServiceTableSerializer
  • Se envía una petición ya con el objeto serializado
  • Se recibe el resultado de la petición (en formato JSON)
  • Finalmente, se convierte el objeto recibido en el enviado, por si los datos hubiesen cambiado.
public async Task InsertAsync(T instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            // Serialize the instance
            JObject value = MobileServiceTableSerializer.Serialize(instance).AsObject();

            // Send the request
            JToken response = await this.InsertAsync(value);

            // Deserialize the response back into the instance in case any
            // server scripts changed values of the instance.
            MobileServiceTableSerializer.Deserialize(response, instance);
        }

El envío de la petición se hace en dos fases, una fase manejada, y otra fase nativa, la fase manejada se hace en un fichero situado en:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.Managed.cs

En el que se realiza la siguiente operación:

public Task<JToken> InsertAsync(JObject instance)
{
    return this.SendInsertAsync(instance);
}

Que nos lleva a este otro fichero (situado en la fase manejada):

internal async Task SendInsertAsync(JObject instance)
{
    if (instance == null)
    {
        throw new ArgumentNullException("instance");
    }

    // Make sure the instance doesn't have its ID set for an insertion
    if (instance.Get(IdPropertyName) != null)
    {
        throw new ArgumentException(
            string.Format(
                CultureInfo.InvariantCulture,
                Resources.CannotInsertWithExistingIdMessage,
                IdPropertyName),
            "instance");
    }

    string url = GetUriFragment(this.TableName);
    JToken response = await this.MobileServiceClient.RequestAsync("POST", url, instance);
    JToken patched = Patch(instance, response);
    return patched;
}

Aquí las operaciones realizadas son:

  • Comprobar que el elemento que estamos insertando no tenga ya un Id, al fin y al cabo es nuevo.
  • Obtener la url de la tabla a la que vamos a insertar el elemento.
  • Enviar una petición HTTP POST al servicio web, con la url que hemos especificado y los datos, y recuperar el resultado.
  • Actualizar el objeto enviado con la respuesta.
  • Devolver el objeto enviado.

Finalmente llegamos a RequestAsync, donde se realiza la petición, situada en este fichero:

src/
  Microsoft.Azure.Zumo.WindowsPhone8.Managed/
    Core/
      MobileServiceClient.cs

Este método genera una petición del tipo deseado, especifica que solamente queremos parámetros en formato JSON, especifica la autenticación a partir de las claves que hemos especificado en la configuración inicial, y finalmente envía la petición:


internal async Task<JToken> RequestAsync(string method, string uriFragment, JToken content)
        {
            Debug.Assert(!string.IsNullOrEmpty(method), "method cannot be null or empty!");
            Debug.Assert(!string.IsNullOrEmpty(uriFragment), "uriFragment cannot be null or empty!");

            // Create the web request
            IServiceFilterRequest request = new ServiceFilterRequest();
            request.Uri = new Uri(this.ApplicationUri, uriFragment);
            request.Method = method.ToUpper();
            request.Accept = RequestJsonContentType;

            // Set Mobile Services authentication, application, and telemetry
            // headers
            request.Headers[RequestInstallationIdHeader] = applicationInstallationId;
            if (!string.IsNullOrEmpty(this.ApplicationKey))
            {
                request.Headers[RequestApplicationKeyHeader] = this.ApplicationKey;
            }
            if (!string.IsNullOrEmpty(this.currentUserAuthenticationToken))
            {
                request.Headers[RequestAuthenticationHeader] = this.currentUserAuthenticationToken;
            }

            // Add any request as JSON
            if (content != null)
            {
                request.ContentType = RequestJsonContentType;
                request.Content = content.ToString();
            }

            // Send the request and get the response back as JSON
            IServiceFilterResponse response = await ServiceFilter.ApplyAsync(request, this.filter);
            JToken body = GetResponseJsonAsync(response);

            // Throw errors for any failing responses
            if (response.ResponseStatus != ServiceFilterResponseStatus.Success || response.StatusCode >= 400)
            {
                ThrowInvalidResponse(request, response, body);
            }

            return body;
        }

Es todo un conjunto de acciones que se llevan a cabo solamente para insertar un elemento. En el próximo artículo veremos cómo se ejecutan las consultas usando la API, y cómo funcionan métodos como este:

List<TodoItem> items = await todoTable
   .Where(todoItem => todoItem.Complete == false)
   .ToListAsync();

Happy Hacking!

Consumiendo datos de Twitter con Javascript usando JSON

Tenía ganas de escribir algo de javascript pero nunca había surgido la ocasión, y surgió, una duda de un amigo que necesitaba tener un timeline de twitter en su web para luego mostrarlo de una manera animada, así que pensé que sería una idea interesante investigar la API de twitter y cómo consumirlo vía Javascript para poder dejar una salida limpia e independiente de plugins.

Continuar leyendo «Consumiendo datos de Twitter con Javascript usando JSON»