sábado, 10 de marzo de 2012

Silverlight front-end – Node.js back-end

He estado haciendo algunos experimentos con Node.js. Hay muchas cosas que ver sobre este tema en internet, desde servicios PaaS (Platform as a service) gratuitos como Nodester para colocar en la nube nuestras aplicaciones Node, interesantes frameworks para crear aplicaciones tipo MVC con Node.js como Express, motores de plantillas para la capa de presentación como Jade, y una plétora interminable de tecnologías interesantes construidas sobre Node.js para cubrir una gama muy amplia de soluciones a clásicos (y no tan clásicos) patrones de desarrollo.

Algo que no he encontrado hasta ahora en internet es algún mecanismo para hacer que una aplicación Silverlight utilice a Node.js en el back-end, así que me puse a probar algunos conceptos y en este post les comparto algunos de los resultados.

¿Porque usar esta peculiar combinación de tecnologías? Bueno, personalmente encuentro a Silverlight como la opción mas avanzada para crear aplicaciones RIA con gran enfoque en la usabilidad y con un enorme poder para desarrollar aplicaciones de línea de negocios (LOB). El pequeño detalle con Silverlight es que cuando toca a la puerta la necesidad de servir la aplicación desde un ambiente “Unixoide” quedamos fuera de combate. Si bien es cierto que existen tecnologías como Moonlight que permiten desarrollar aplicaciones con un clon de Silverlight para multiplataforma basado en Mono, hay muchos detalles que no están disponibles aun en Moonlight como por ejemplo el potente Entity Framework de Microsoft para desarrollar lógica compleja en una robusta capa de persistencia o bien las grandes ventajas de Visual Studio y el conjunto inagotable de infraestructuras .NET que facilitan enormemente el trabajo.

Entonces ¿que hacer si queremos tomar ventaja de las bondades de Silverlight en una aplicación RIA con interface de usuario sofisticada y a la vez necesitamos colgar toda la parte de servicios de datos en un eficaz servidor Linux usando una base de datos open source como Postgresql u otras y aprovechando tecnologías como Node.js?

En realidad no estoy totalmente seguro Risa

Pero de algo si estoy seguro… al hacer algunas pruebas he logrado establecer una mecánica para consumir datos vía servicios REST creado con Node.js desde una capa de cliente Silverlight.

El experimento consta de varias partes que explico a continuación:

Ingredientes
------------

  • Visual Studio 2010 con .NET Framework 4.5
  • Node.js y el módulo Express que usaremos para enrutar las peticiones hechas al servicio desde Silverlight
  • Opcionalmente se puede usar un módulo de acceso a datos para Node.js, por ejemplo pg para bases de datos Postgresql. Aunque este ejemplo lo mantendré simple y pondré datos manualmente construyéndolos en una estructura XML

Esta fuera del alcance de este articulo cualquier detalle de la instalación y uso de los módulos y herramientas señalados en los ingredientes. En los respectivos sitios web de cada cosa hay instrucciones y ejemplos suficientes.

Creando un pequeño servicio RESTful con Node.js

Con el siguiente código tendremos lo suficiente para exponer un servicio accesible a través del protocolo HTTP.

Contenido del archivo rest.js

   1:  var express = require('express');
   2:  var app = express.createServer();
   3:   
   4:  /*
   5:  Necesitamos que el servicio pueda exponer contenido estático 
   6:  para que silverlight pueda consumirlo usando clientaccesspolicy.xml
   7:  explicaciones aquí: 
   8:  http://msdn.microsoft.com/en-us/library/cc197955(v=vs.95).aspx
   9:  */
  10:  app.use("/", express.static(__dirname + '/'));
  11:   
  12:  var output = "";
  13:   
  14:  app.get('/rest', function(req, res) {
  15:   
  16:      output = "";
  17:      output += "<?xml version ='1.0' encoding='utf-8'?>";    
  18:      output += "<Users>";
  19:      output += "<User>";
  20:      output += "<Name>Pancho Villa</Name>";
  21:      output += "<Age>24</Age>";
  22:      output += "</User>";
  23:      output += "<User>";
  24:      output += "<Name>Spider Man</Name>";
  25:      output += "<Age>25</Age>";
  26:      output += "</User>";
  27:      output += "<User>";
  28:      output += "<Name>Edgar A. Poe</Name>";
  29:      output += "<Age>200</Age>";
  30:      output += "</User>";
  31:      output += "</Users>";
  32:   
  33:      res.send(output);
  34:  });
  35:   
  36:  app.listen(1337);



Este script es muy simple. Su tarea es definir la ruta de acceso a funciones javascript cuya respuesta arrojará un resultado en forma de XML vía HTTP. Si se utiliza un modulo de acceso a datos se puede fácilmente hacer que la respuesta se base en una consulta a una base de datos en vez de introducir la información manualmente.


La línea 10 del código establece una ruta ‘/’ que se refiere a la raíz del sistema de archivos donde se encontrará alojado el servicio. Esta ruta define un espacio donde se deberá colocar un archivo XML cuyo nombre debe ser exactamente clientaccesspolicy.xml que tiene por objetivo permitir a Silverlight confiar en el contenido del servicio. Esto se explica aquí.


El contenido del archivo clientaccesspolicy.xml es el siguiente para este ejemplo:


<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*" http-methods="GET">        
        <domain uri="*" />
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>



En el caso de este ejercicio el archivo clientaccesspolicy.xml debe existir forzosamente en la ruta señalada ‘/’ es decir, exactamente en la misma ubicación (directorio raíz de nuestro servicio) que el archivo rest.js que contiene el script de Node.js.


Para ejecutar el servicio utilizamos el siguiente comando (dentro del folder donde se hayan colocados los archivos clientaccesspolicy.xml y rest.js):


(ya sea en *nix)
$node rest.js


(o bien en Windows)
>node rest.js


Lo anterior ejecuta el script de Node.js que estará escuchando en el puerto 1337 según nuestro ejemplo.


Esto es todo lo que necesitamos con respecto al lado del servidor.


 


Creando el cliente Silverlight para consumir el servicio


Ahora necesitamos un cliente que despliegue la información generada en el servicio que acabamos de crear.


Desde Visual Studio creamos una nueva aplicación Silverlight. Para este ejemplo agrego un DataGrid como se muestra en el siguiente código:


...
 
        <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Vertical">
            <Border Background="#FF7BAED1" 
                    BorderThickness="2" 
                    BorderBrush="#FF0A0A0A">
                <TextBlock Text="Consumir REST con Silverlight" 
                           FontSize="12" 
                           FontWeight="Bold"></TextBlock>
            </Border>
 
            <sdk:DataGrid AutoGenerateColumns="True" 
                          Name="dataGrid1" 
                          HorizontalAlignment="Stretch" 
                          Height="150" 
                          VerticalAlignment="Stretch" 
                          UseLayoutRounding="True" 
                          Margin="10,100,10,0">
                
            </sdk:DataGrid>
        </StackPanel>                   
 
    </Grid>
...



Después de esto habrá que añadir una referencia al namespace System.Xml.Linq e incluir en las clausulas using del code behind (C# en este caso) la referencia:


using System.Xml.Linq;


Para este ejercicio vamos a usar Linq to XML para manejar algunos detalles de la conversión de los datos obtenidos a través de Http pero también es posible usar técnicas de serialización-deserialización que no veremos aquí.


El siguiente es el código (code behind) en C# se omiten partes no relevantes para fines de aprovechar mejor el espacio:




Creamos una clase dentro del code behind que concuerde con la estructura de los nodos especificados en el XML que se genera dentro del servicio en el archivo rest.js.


public class User
{
    [XmlElement]    
    public string Name { get; set; }
 
    [XmlElement]
    public int Age { get; set; }
}



Es importante que el nombre de la clase sea el mismo que el nombre del nodo raíz de la estructura XML y que los nombres de las propiedades correspondan de igual forma a los nombres de los atributos del XML.


El siguiente código muestra como implementar el resto del cliente.




   1:  namespace SLREST
   2:  {
   3:      public partial class MainPage : UserControl
   4:      {
   5:          public MainPage()
   6:          {
   7:              InitializeComponent();
   8:              
   9:              Uri serviceUri = new Uri("http://127.0.0.1:1337/rest");            
  10:              WebClient cliente = new WebClient();
  11:              
  12:              cliente.OpenReadCompleted += new OpenReadCompletedEventHandler(cliente_OpenReadCompleted);
  13:              cliente.OpenReadAsync(serviceUri);
  14:   
  15:          }
  16:   
  17:          void downloader_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
  18:          {
  19:              if (e.Error == null)
  20:              {
  21:                  Stream responseStream = e.Result;
  22:                  XDocument oDoc = XDocument.Load(responseStream);
  23:                  var Data = from info in oDoc.Descendants("User")
  24:                             select new User
  25:                             {
  26:                                 Name = Convert.ToString(info.Element("Name").Value),
  27:                                 Age = Convert.ToInt32(info.Element("Age").Value)
  28:                             };
  29:                  dataGrid1.ItemsSource = Data;
  30:                  
  31:              }
  32:              else
  33:              {
  34:                  txtOutput.Text = e.Error.ToString();
  35:              }
  36:          }
  37:      }
  38:  }



En el método MainPage se instancia un cliente web (de tipo WebClient) que apunta directamente al servicio creado con Node.js. Para este ejemplo tanto el cliente Silverlight como el servicio rest.js viven en el mismo servidor local.


Después de inicializar esto, se procede a crear un evento que se ejecutara cuando, en forma asíncrona, el cliente web solicite la petición al servicio. Al ser una llamada asíncrona toda la acción recae sobre este evento en donde, finalmente, se crea un objeto de tipo XDocument que contendrá la respuesta a la invocación del servicio. Usando Linq obtenemos los datos XML  y usando un objeto del tipo User (clase que creamos al inicio del code behind) para luego asignarlos a la propiedad ItemSource del DataGrid que definimos anteriormente en el XAML.


el resultado final luce así:


ss-slrst


 


Con muy poco código obtenemos el despliegue de datos en un grid muy bueno con capacidades para mover las columnas y ordenamiento en base a columnas con un simple click.


Este es un ejemplo muy sencillo pero la finalidad es dar una idea y un punto de partida que nos pueda servir para incursionar mas tarde en soluciones mas elaboradas y completas.