Desarrollo colaborativo con VS Anywhere (¡y sorteo de licencias!)

Ya sabéis que me encantan las aplicaciones colaborativas en tiempo real, ¿verdad? Bien, y si aplicamos este concepto a nuestro entorno de desarrollo favorito, Visual Studio, ¿qué podemos esperar? Pues seguro que un producto espectacular.

En este artículo vamos a ver echar un vistazo a VS Anywhere, un producto del que seguro habéis oído hablar,  que supone una revolución en la forma en que podemos colaborar en tiempo real con colegas y compañeros desarrolladores utilizando Visual Studio, sin importar donde estos se encuentren.

Pero antes de empezar, deciros que al final del artículo encontraréis las instrucciones para participar en el sorteo de 5 licencias de servidor por cortesía de VS Anywhere. ¡No dejéis de participar!

1. Qué es VS Anywhere

VS Anywhere es una extensión para Visual Studio 2013, 2012 y 2010 que permite que equipos de trabajo remotos colaboren sobre la misma base de código en tiempo real. Es decir, no se trata de una solución de control de código fuente o de simple comunicación: son desarrolladores que pueden estar tocando simultáneamente el mismo proyecto y viendo “en vivo” lo que otros hacen en el mismo.

Va mucho más allá que la simple compartición de pantalla o el control remoto de un equipo, es colaboración al puro estilo Google Docs, pero en Visual Studio y con herramientas específicamente diseñadas pensando en los desarrolladores.

Esto abre unas posibilidades interesantísimas en el campo de la colaboración para equipos de trabajo remotos, formación, consultoría, asistencia, etc. De hecho, algunos escenarios en los que VS Anywhere encaja de forma natural en equipos distribuidos que necesitan trabajar en tiempo real son:

  • Definición conjunta de arquitecturas o pruebas de concepto.
  • Coaching, tutorización y seguimiento.
  • Revisiones de código.
  • Transmisión de conocimiento.
  • Pair programming.
  • Pruebas de selección de personal.
  • Mentoring.
  • Soporte técnico y resolución de incidencias.

Se trata de un producto que se comercializa en régimen de suscripción mensual con costes por servidor (cloud/on premise) y usuario, pero es gratuito para estudiantes o proyectos open source. De hecho, es posible probarlo utilizando los servidores gratuitos de VS Anywhere, sólo hay que registrarse y descargar la extensión desde la galería de Visual Studio.

En su sitio web, vsanywhere.com, y en su sitio de soporte, http://support.vsanywhere.com, podéis encontrar documentación, vídeos, tutoriales y descripciones mucho más formales y concienzudas de lo que vamos a ver aquí 😉

2. Instalación

La instalación de la extensión puede realizarse desde el propio Visual Studio, accediendo a la galería de extensiones del entorno de desarrollo. Basta con buscar “VS Anywhere” y pulsar el botón de descarga correspondiente para que se instale automáticamente:

Búsqueda en la biblioteca de extensiones
Una vez finalizada la instalación y reiniciado el entorno, tendremos a nuestra disposición una nueva opción en el menú de Visual Studio, a través de la cual podremos acceder a las ventanas y herramientas de este complemento.

Menú VS Anywhere
Además de esta nueva opción en el menú, VS Anywhere añadirá a nuestro Visual Studio un par de ventanas nuevas, que pueden ser acopladas o movidas a donde más nos interese:

  • VS Anywhere control center, como su nombre indica, es la ventana desde la cual gestionaremos prácticamente todo: nuestros contactos, grupos, sesiones, servidores, etc.
  • Otra ventana llamada VS Anywhere a secas, que es donde se mostrará información genérica del producto, y desde donde realizaremos la interacciones vía chat con nuestros contactos o grupos de ellos.

Ventanas añadidas por VS Anywhere

Pero bueno, salvo curiosear por estas herramientas, nada podremos hacer si no nos registramos como usuarios de VS Anywhere, así que vamos allá…

3. Registro e identificación de usuarios

Obviamente, para poder colaborar en tiempo real con otros usuarios, necesitamos disponer de un servidor central que se encargue de coordinar la comunicación y acciones entre todos los extremos. Si nos interesan las opciones comerciales del producto, podemos contratar el servicio alojado en la nube o bien utilizar infraestructura on-premise, pero VS Anywhere ofrece la posibilidad de utilizar servidores públicos gratuitos con los que podremos probar el producto completo, sólo a cambio de completar un sencillo formulario de registro disponible siguiendo este enlace.

Podemos registrando usando nuestras credenciales de Facebook, Microsoft, Google o Yahoo, o bien introducir nuestro nombre, email y elegir una contraseña de acceso. En cualquier caso, los datos de contacto son importantes porque son los que utilizarán otros usuarios de la herramienta para localizarnos y que podamos comenzar a trabajar conjuntamente.

Hecho esto, ya podemos iniciar la sesión en el servidor de pruebas (public.vsanywhere.com) utilizando la opción “Connect to server” en el menú de VS Anywhere:

image

4. Seamos sociales

Una vez identificados en un servidor de VS Anywhere, ya apareceremos en su directorio de usuarios y, por tanto, estaremos disponibles para que otros usuarios puedan contactar con nosotros. Si, en cambio, queremos tomar la iniciativa, también podremos buscar a nuestros contactos o compañeros en dicho directorio.

Como era de esperar, el mecanismo es bien sencillo: basta con pulsar el botón correspondiente a añadir contactos en el Control Center o en el menú principal y aparecerá un buscador en el que podremos localizar a nuestros compañeros utilizando su nombre o email:

Añadir contactos

Entre dos usuarios sólo puede haber una relación consentida por ambos; es decir, desde este punto enviaremos una solicitud al usuario indicándole que queremos convertirlo en nuestro contacto, pero esto sólo ocurrirá si él lo acepta. Vaya, como en Facebook 😉

Desde el punto de vista opuesto, cuando otro usuario solicita añadirnos como contacto, VS Anywhere nos notificará y podremos decidir si nos interesa:

Solicitud de un contacto
Conforme vayamos siendo aceptados, los contactos irán apareciendo en el Control Center de VS Anywhere, donde podremos ver si están o no conectados, e incluso saber en tiempo real qué están haciendo en Visual Studio (!):

Contactos

5. Preparando la fiesta

Las funcionalidades y herramientas más espectaculares de VS Anywhere giran en torno a la sesión. Una sesión es la unión de varios usuarios trabajando simultáneamente sobre un proyecto que uno de ellos debe haber compartido con el resto. Este proceso se inicia cuando, con una solución VS activa, pulsamos el botón “Share project” o “Share solution” desde el centro de control o el menú principal, o bien desde los propios proyectos usando el menú contextual en el explorador de soluciones.

En cualquier caso debemos decidir qué proyecto o proyectos deseamos compartir en la sesión, y con cuáles de nuestros contactos queremos hacerlo. Estas opciones se configuran en un cuadro de diálogo:

Configurar sesión de trabajo

Los usuarios participantes recibirán una notificación indicándoles que han sido invitados a participar en una sesión en la que se está compartiendo un proyecto o solución determinada. Desde esta misma notificación, los invitados podrán acudir al panel de VS Anywhere para aceptar o declinar dicha invitación:

Invitación a sesión
Para poder disfrutar de la colaboración en tiempo real sobre Visual Studio, es importante que todos los participantes partan de una base común de código. Tened en cuenta que con VS Anywhere el usuario que inicia la sesión de trabajo no está compartiendo pantalla ni cediendo el control de su máquina a los invitados: cada uno trabajará en local con su propia copia del proyecto, pero los cambios o acciones que vaya realizando se propagarán automáticamente al resto de participantes.

Por esta razón,  para asegurar que el punto de partida es el mismo, es importante que desde el principio se realice una sincronización del proyecto en todos los extremos participantes. Así, si nos han invitado a unirnos a una sesión relativa a un proyecto cuyo código fuente no tenemos en nuestra máquina, VS Anywhere nos mostrará un cuadro de diálogo como el siguiente:

Conflicto: no tenemos el proyecto
Desde ahí, el botón “Get a Solution copy from the host”, como su nombre indica, iniciará la descarga del proyecto directamente desde el host, aunque se comprimirá antes de enviárnosla para acelerar el proceso.

image

Este punto puede ser un poco delicado con proyectos de cierto tamaño. Por ejemplo, si estoy trabajando desde casa y comparto mi proyecto con varios colaboradores que no lo tenían previamente, deberán descargárselo completo desde mi equipo, y el ancho de banda de subida de mi ADSL doméstica puede resultar insuficiente para poder ofrecer una velocidad razonable. Por ello, VS Anywhere permite también la integración con Github, de forma que los participantes podrían obtener el código fuente directamente desde una rama, changeset o etiqueta de este repositorio, acelerando drásticamente la sincronización inicial del proyecto.

También podría pasar que los participantes tuvieran su propia copia previa del proyecto, ya sea habiéndola compartido previamente, o lo recomendado, usando un control de código fuente, y ésta podría o no estar actualizada respecto a la usada por el organizador de la sesión. VS Anywhere detectará este escenario y mostrará un cuadro de diálogo muy parecido al anterior, pero nos avisará de que existe un conflicto de versiones:

Conflicto: proyecto no sincronizado
Los conflictos de versiones pueden ser solucionados con herramientas de Diff y Merge parecidas a las que encontramos en sistemas tradicionales de control de código fuente:

Diff y merge en VS Anywhere

6. ¡Y que comience el espectáculo!

Una vez los invitados van actualizando su copia local del proyecto, irán uniéndose a la sesión de forma efectiva, y comenzaremos a ser conscientes del potencial de VS Anywhere.

Sesión

Sin duda, lo más espectacular es la edición colaborativa de código sobre Visual Studio. Ver otros usuarios modificando un archivo al mismo tiempo que nosotros es realmente asombroso, a la vez que un poco espeluznante ;-). La siguiente captura de pantalla muestra cómo vemos en tiempo real a un usuario modificando el mismo archivo que estamos retocando nosotros:

image

Esta característica es impresionante en algunos de los escenarios que describimos al comienzo del post por la gran sensación de interactividad y colaboración que aporta. Por ejemplo, en una sesión de formación, que los alumnos puedan ver los pasos que da el formador es genial, pero es algo que podríamos conseguir con un simple software de compartición de pantalla; aquí podemos ir más allá, puesto que el profesor podría solicitar a un alumno que implemente una característica directamente sobre el proyecto, y todos iríamos viendo sobre la marcha los pasos que está dando. También en consultoría, coaching, o soporte técnico encajaría de forma muy natural, y las posibilidades para programación por parejas en remoto (pair programming) son interesantísimas.

Hay otros escenarios a priori muy atractivos donde esta colaboración en tiempo real podría resultar algo más peligrosa, pues podemos vernos “salpicados” por problemas causados por otras manos sobre el proyecto. Por ejemplo, si un desarrollador está creando la característica X y durante su implementación se están produciendo errores de compilación (lo cual es muy normal), el resto de usuarios no podrían probar sus respectivos desarrollos porque el proyecto no compilaría. Como casi todo en esta vida, con VS Anywhere también hay que saber cómo y cuándo aplicarlo, y hacerlo con sentido común.

Pero las herramientas de colaboración a nivel de código no quedan ahí. En todos los archivos que abramos aparecerá en la esquina superior derecha una indicación de los usuarios que lo tienen actualmente abierto, o incluso una marca mostrando su posición exacta dentro de la barra de scroll. Pulsando sobre sus nombres, al igual que si lo hacemos desde el Control Center, podremos ir justo al archivo y posición en la que estén trabajando en ese momento.

Otras características interesantes y útiles son la posibilidad de hacer un “follow” o un “highlight” a un usuario concreto. En el primer caso, pondremos nuestro Visual Studio con el piloto automático para que siga al usuario indicado, de forma que nuestro entorno de desarrollo irá mostrando todos los archivos que vaya usando este individuo, así como los cambios realizados en tiempo real. En el segundo caso, indicamos que los cambios realizados por un usuario concreto aparecerán destacados sobre el resto visualmente, para distinguirlos de modificaciones de otros que quizás nos interesen menos (por ejemplo, durante una formación los alumnos podrían querer destacar lo que vaya haciendo el profesor sobre cambios realizados por otros alumnos).

Herramientas VS Anywhere

Como indican en la web del producto, la colaboración no sólo se extiende a nivel de código fuente, también otros tipos de archivos y diseñadores, como el de Web Forms o XAML.

ChatPor último, a la hora de finalizar la sesión, podemos decidir si mantener los cambios realizados en el proyecto o dar marcha atrás y dejarlo como estaba cuando la comenzamos, puesto que se realiza una copia de seguridad automática al comenzar. Esto puede ser muy interesante, por ejemplo, para realizar pruebas de concepto o formación sobre proyectos reales.

7. Otras herramientas

VS Anywhere viene, además acompañado de otras herramientas adicionales que, si bien no son tan novedosas como las descritas anteriormente, son un complemento ideal para facilitar la colaboración remota.

Por ejemplo, no podía faltar un chat entre usuarios del sistema, tanto de forma privada como a nivel grupal, que pueden configurarse e iniciarse en cualquier momento.

Tampoco podía faltar, obviamente, una utilidad para el envío directo de archivos a usuarios conectados:

Enviar archivos

 

 

 

 

 

Y para facilitar la comunicación por voz, VSAnywhere incluye una sencilla integración con Skype que permite asociar cuentas de esta herramienta de video/audio conferencias con usuarios de la aplicación, de forma que las llamadas puedan realizarse desde el mismo entorno.

8. Últimas noticias: VS Anywhere, rizando el rizo

Hace unos días los usuarios de VS Anywhere hemos recibido un mail notificando la aparición de una actualización de la herramienta con algunas novedades bastante interesantes, a saber:

  • Sesiones moderadas
  • Merge cooperativo
  • Y, la que es sin duda más espectacular, web workspaces.

Si quieres saber más sobre ellas, puedes leer el artículo completo en Variable not found.

9. Participación en el sorteo

Realmente, si estáis interesados podéis descargar la extensión desde la galería de Visual Studio y comenzar a trabajar con ella para haceros una idea del producto de primera mano. No necesitáis nada más; podéis probar por completo VS Anywhere, las únicas limitaciones que tendréis serán relativas a la velocidad, puesto que es un servidor compartido, y a la seguridad, dado que las comunicaciones no serán cifradas.

Pero de todas formas, los chicos de VS Anywhere han tenido la amabilidad de ofrecer 5 licencias de servidor con 5 desarrolladores cada una por tres meses, para lectores de Variable not found, valoradas en 300 euros cada una. Como en otras ocasiones, las sortearemos entre todos aquellos que durante los próximos siete días me hagáis llegar vuestro interés en conseguir esta licencia mediante alguna de las vías disponibles (un comentario en este post, el formulario de contacto, Twitter, Variable not found en Facebook, señales de humo, o como sea ;-)).

No olvidéis facilitar una forma de contactar con vosotros por si resultáis premiados. Ojo, no pongáis vuestros mails directamente en comentarios porque los spammers están siempre al acecho, enmascaradlo de alguna forma creativa, pero que se entienda (por ejemplo “pedro punto lopez arrob@ ge mail punt0 c0m”).

¡Suerte!

Publicado en Variable not found

Gracias a Miguel del Valle (@midellav) por su inestimable colaboración a la hora de escribir este post 🙂

Si usas GUIDs, mucho ojo el próximo 11 de febrero

GUID Colliding SequenceLos GUID (Globally Unique Identifier) son una serie de 16 bytes generados pseudoaleatoriamente que se suelen utilizar para identificar de forma única objetos, dispositivos, componentes, o prácticamente cualquier cosa. Y, a diferencia de otros tipos de identificadores, el “única” debe ser interpretado de forma global, es decir, es virtualmente imposible que un GUID utilizado para un elemento se repita en otro equipo, lugar o sistema.

 

No hay magia negra en esto, es pura matemática. El número de combinaciones posibles es 2128; dicho así no parecen demasiadas, pero para que os hagáis una idea de las magnitudes, serían algo así como 3.400.000.000.000.000.000.000.000.000.000.000.000.000 valores distintos, número arriba o número abajo ;-). Si os parece difícil que os toque la lotería, supongo que podréis intuir lo poco probable que es que existan colisiones si decidimos seleccionar un GUID, por ejemplo, como campo clave en filas de una base de datos, o como nombres de fichero que queremos asegurar que sean únicos. Y así lo hemos hecho durante años.

 

Y todos los que los usamos seguiríamos tan felices si no fuera por ese pequeño matiz que he deslizado al comienzo del post: la pseudoaleatoriedad del algoritmo de generación utilizado, que va a provocar un colapso el próximo 11 de febrero debido al efecto denominado GUID Colliding Sequence.

 

Este efecto, que ocurre cada 999.999.999 segundos, de hecho ya apareció el 4 de junio de 1982 y afectó a algunos microordenadores y mainframes de la época. Por ejemplo, el famoso Spectrum se vio afectado y tuvieron que retirar todas las unidades un mes después de salir al mercado, y lo mismo ocurrió con el célebre IBM 3081, cuyos problemas de funcionamiento llegaron a tambalear a la industria tabaquera estadounidense, donde estaban muy implantados.

 

Números aleatoriosEl problema se debe, básicamente, a un error en la definición del algoritmo para generación de GUIDs recogida por el estándar definido en la RFC 4122 que podéis leer aquí. Como se ver en el texto (pág 10), el almacén utilizado para guardar el número de intervalos de cien nanosegundos transcurridos desde las 0:00:00 del 15 de octubre de 1582 utiliza valores de 60 bits, y con un poco de calculadora veréis que el próximo 11 de febrero se llegará al valor máximo permitido antes de producirse de nuevo un overflow. Y combinando esto con el resto de elementos que se tienen en cuenta para generar los identificadores y las operaciones lógicas que se realizan entre ellos, existe más de un 92% de probabilidades de que los GUID generados dicho día colisionen entre sí.

 

Bueno, ¿y qué podemos hacer? Pues aparte de cruzar los dedos, la OMC (Organización Mundial del Comercio) recomienda observar, entre otras, las siguientes precauciones antes y durante el 11 de febrero. Algunas recomendaciones van dirigidas a profesionales de nuestro sector, y otras a los usuarios en general:

  • Modificar todas las aplicaciones que usen GUIDs para identificar elementos, de forma que dejen de asumir su unicidad universal. Se recomienda crear factorías que, antes de retornar el control a funciones de orden superior, comprueben en un bucle la no existencia previa de los identificadores generados, volviendo a generar un GUID distinto cuando se detecte una colisión.
  • Programar turnos de vigilancia de los sistemas, especialmente las bases de datos, durante las 24 horas del 11 de febrero. Programar alertas o triggers que nos avisen de duplicidades e inconsistencias en la información manejada.
  • No publicar contenidos en blogs y redes sociales, pues habitualmente se referencian con un GUID (Twitter, Facebook, Pinterest y Yammer han confirmado este extremo) y podrían perderse.
  • Por la misma razón, tampoco es recomendable subir archivos a servicios en la nube como Google Drive, Skydrive, Dropbox, Instagram o Picasa.
  • No realizar compras, ni movimientos bancarios, ni, en general, transacciones que puedan estar almacenándose internamente utilizando GUIDs, porque podrían no guardarse correctamente y podría volatilizarse el dinero.
  • No fabricar dispositivos que puedan utilizar GUIDs como identificadores únicos, como las direcciones MAC de las tarjetas de red o pens USB. Es decir, lo mejor es parar las fábricas.
  • No iniciar procedimientos administrativos para evitar pérdidas de información, como presentar documentación en un Ayuntamiento, o justificantes en Hacienda. Otra consecuencia, aunque esta vez positiva, es que una multa de tráfico registrada ese día podría no llegarnos jamás, pues la DGT (Dirección General de Tráfico de España) utiliza GUIDs como identificadores 😉
  • No usar firma electrónica, puesto que el hash puede contener trazas reconocibles y reproducibles por otros usuarios para realizar firmas en nuestro nombre.
  • No adquirir participaciones de lotería o sorteos que sean generadas en máquinas expendedoras, pues podría darse el caso de que a todos los jugadores se asignen los mismos números y tendríamos que repartir los beneficios con un buen puñado de gente.
  • … estas son las más destacables, pero podéis leer la lista completa en la web de la OMC.

Como curiosidad adicional, deciros que esto no pasaría de ser un puro problema técnico similar al ya vivido en el año 2000 si no fuera porque hace unos años el Consejo Europeo de Organizaciones Escépticas  (sí, los mismos que ofrecieron un millón de dólares a quien demostrara tener poderes paranormales), ofrecieron la nada despreciable cantidad de quinientos mil euros a quien demostrara un caso real de repetición de un GUID. Si detectáis una duplicidad no dejéis de hacérsela llegar, porque podéis llevaros una buena alegría, aunque podréis imaginar también la que se va a liar en el buzón de correo de esta gente el 11 de febrero ;-DD

 

La siguiente vez que ocurra será el viernes 20 de octubre de 2045. A saber dónde andamos para entonces, así que, de momento, mejor preocuparnos exclusivamente por el problema que se avecina en un par de meses.

 

¡Suerte y mucho ojo!

 

Fuentes:

Publicado en Variable not found con GUID 8deda7b6-021d-4c5f-8929-89eb40a73a91.

 

[Actualizado 30/12]
Nota para despistadillos: obviamente la noticia no es real, se trata simplemente de una broma del Día de los Inocentes. El próximo 11 de febrero podéis seguir usando GUIDs, fabricando dispositivos,  comprando por internet, usando la firma electrónica y participando en sorteos. Ah, y las multas de tráfico os llegarán a casa, no os confiéis :-DDD

Inyección de parámetros en acciones ASP.NET MVC (y III)

ASP.NET MVCEn este tercer y último post de la serie (ir al primero o segundo) , vamos a ver una última técnica para realizar inyección de parámetros a acciones ASP.NET MVC que, aunque aporta menos a la hora de comprender las misteriosas interioridades del framework, es ciertamente mucho más cómoda y práctica en caso de que deseemos aplicar esta técnica.

Recapitulando un poco, queremos pasar de un planteamiento en el que el controlador es el que recibe mediante su constructor todas las dependencias que necesitan sus acciones, a otro en el que sean las propias acciones las que reciben los componentes de los que dependen.

Por tanto, pasaríamos de esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HomeController: Controller
{
    private readonly INotificationServices _notificationServices;
    private readonly IProductServices _productServices;
 
    public HomeController(INotificationServices notificationServices,
                          IProductServices productServices)
    {
        _notificationServices = notificationServices;
        _productServices = productServices;
    }
 
    [HttpPost]
    public ActionResult Contact(ContactViewModel vm)
    {
        if (!ModelState.IsValid) return View();
        _notificationServices.NotifyContact(vm.Name, vm.Email, vm.Text);
        return RedirectToAction("Thankyou");
    }
 
    public ActionResult Products()
    {
        return View(_productServices.GetAll());
    }
 
    [...]
}

A esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HomeController: Controller
{
    public HomeController()
    {
    }
 
    [HttpPost]
    public ActionResult Contact(ContactViewModel vm,
                                INotificationServices notificationServices)
    {
        if (!ModelState.IsValid) return View();
        notificationServices.NotifyContact(vm.Name, vm.Email, vm.Text);
        return RedirectToAction("Thankyou");
    }
 
    public ActionResult Products(IProductServices productServices)
    {
        return View(productServices.GetAll());
    }
 
    [...]
}

Pero ojo, antes de continuar, un pequeño disclaimer:

Esta técnica no es especialmente recomendable en la mayoría de escenarios, puesto que puede ocultar dependencias y propiciar la aparición de controladores extensos y con demasiadas responsabilidades. Aún así, es un ejercicio interesante para conocer los mecanismos de funcionamiento internos del framework ASP.NET MVC.

En los artículos anteriores hemos visto cómo conseguirlo manualmente usando dos puntos de extensibilidad del framework, el action invoker y el model binder. En esta ocasión vamos a ver cómo Autofac, el popular contenedor de IoC, nos lo da prácticamente solucionado.

Lo primero que tenemos que hacer, como siempre, es instalar la extensión específica para MVC 4 a través de Nuget:

PM> install-Package Autofac.Mvc4
Attempting to resolve dependency 'Autofac (≥ 3.0.0)'.
Installing 'Autofac.Mvc4 3.0.0'.
Successfully installed 'Autofac.Mvc4 3.0.0'

A continuación, creamos una clase en App_Start para configurar los aspectos relativos al contenedor de inversión de control e inyección de dependencias:

public static class IocConfig
{
    public static void Setup()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ProductServices>()
               .As<IProductServices>().InstancePerHttpRequest();
        builder.RegisterType<NotificationServices>()
               .As<INotificationServices>().InstancePerHttpRequest();

        builder.RegisterType<ExtensibleActionInvoker>()
               .As<IActionInvoker>()
               .WithParameter("injectActionMethodParameters", true);

        builder.RegisterControllers(Assembly.GetExecutingAssembly())
               .InjectActionInvoker();

        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 
    }
}

En el código anterior encontramos:

  • En primer lugar, el registro de los interfaces IProductServices e INotificationServices, para que puedan ser resueltos correctamente.  
  • A continuación se registra la clase ExtensibleActionInvoker, propia de Autofac, como el action invoker a usar en nuestra aplicación. Se le indica, además, que debe inyectar valores para los parámetros de las acciones, que es justo lo que andábamos buscando.  
  • Por último, se registran los controladores con el nuevo action invoker, y se establece como dependency resolver del sistema el facilitado también por Autofac.

Una vez hecho esto, sólo necesitamos llamar al método Setup() desde el global.asax.cs para que se ejecute al arrancar el sistema, ¡y esto es todo! Con esos simples pasos tendremos funcionando nuestra aplicación ASP.NET MVC con inyección de parámetros automática, utilizando para su resolución el contenedor de IoC Autofac.

El ejemplo desarrollado en este post podéis descargarlo desde Skydrive.

Publicado en http://www.variablenotfound.com/2013/06/inyeccion-de-parametros-en-acciones_18.html

Inyección de parámetros en acciones ASP.NET MVC (II)

ASP.NET MVCEn el primer post de la serie, vimos rápidamente en qué consiste la inyección de parámetros como fórmula para suministrar dependencias a una acción ASP.NET MVC, y realizamos una implementación sustituyendo unas pequeñas piezas del framework llamados action invokers que, como su nombre indica, son los responsables de invocar las acciones solicitadas por el usuario.

La idea es poder tener en nuestros controladores acciones que reciben directamente un interfaz que es resuelto justo en el momento de ejecutarla, es decir, algo así:

1
2
3
4
public ActionResult Products(IProductServices productsServices)
{
    return View(productsServices.GetAll());
}

Si intentamos ejecutar directamente esta acción en ASP.NET MVC, veremos que aparece un error indicando que no se puede crear una instancia de un interfaz. Lo que pretendemos conseguir, esta vez utilizando model binders, es que cuando se detecte un parámetro de este tipo, se resuelva la dependencia utilizando, por ejemplo, un contenedor IoC.

Pero antes de ver cómo, va una recomendación:

Disclaimer: como ya comenté en el artículo anterior, esta técnica no es especialmente recomendable en la mayoría de escenarios, puesto que puede ocultar dependencias y propiciar la aparición de controladores extensos y con demasiadas responsabilidades. Aún así, es un ejercicio interesante para conocer los mecanismos de funcionamiento internos del framework ASP.NET MVC.

Inyección de parámetros usando model binders

Los model binders son los componentes usado por el framework para obtener los valores para cada uno de los parámetros requeridos por una acción. Es decir, si en una acción tenemos un parámetro de tipo XYZ, será un model binder el que se encargue de buscar valores para el mismo en el contexto de la petición apoyándose a su vez en otros componentes llamados value providers, aunque para nuestros objetivos éstos no tienen importancia.

Por defecto, el proceso de binding de ASP.NET MVC es realizado por la clase DefaultModelBinder, aunque es posible, y además bastante fácil, crear una implementación alternativa y tomar el control durante este proceso simplemente heredando de ella y sobrescribiendo los métodos que nos interesen.

En particular, nos interesaría tomar el control en el momento de creación de las instancias de los parámetros de tipo referencia, que se encuentra en el método CreateModel() del binder. En ese punto podríamos comprobar si se está intentando construir un objeto a partir de un tipo interfaz, para proporcionárselo nosotros a través del dependency resolver y evitar el error que comentábamos antes.

Bien, pues tan sencillo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DependencyInyectorModelBinder: DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext,
                                          ModelBindingContext bindingContext,
                                          Type modelType)
    {
        if (modelType.IsInterface)
        {
            return DependencyResolver.Current
                                     .GetService(modelType);
        }
        else return base.CreateModel(controllerContext,
                                     bindingContext, modelType);
    }
}

El siguiente paso, como en otras ocasiones, sería hacer que el framework utilice este binder, para lo cual sólo tenemos que introducir el siguiente código para que sea ejecutado durante la inicialización del sistema:

1
ModelBinders.Binders.DefaultBinder = new DependencyInyectorModelBinder();

Observad que en este momento, cada vez que el binder se encuentre con un parámetro de tipo interfaz, acudirá al dependency resolver para intentar obtener una instancia. Lo último que tenemos que hacer sería encargarnos de dicha resolución.

En el post anterior vimos cómo hacerlo directamente implementando un dependency resolver personalizado, pero podemos hacerlo incluso de forma más fácil si utilizamos Unity o cualquier otro contenedor de inversión de control. En este caso, podríamos instalar el paquete Unity.Mvc y registrar nuestras clases de la forma habitual:

1
2
3
4
5
6
7
public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<IProductServices, ProductServices>(
                    new PerRequestLifetimeManager());
    container.RegisterType<INotificationServices, NotificationServices>(
                    new PerRequestLifetimeManager());
}

A diferencia del artículo anterior, en este caso sí estaríamos gestionando correctamente el ciclo de vida de las dependencias, puesto que serían liberadas automáticamente al finalizar la petición. Lo único que no debemos olvidar es introducir también esta inicialización del contenedor en el proceso de arranque de la aplicación.

Resumen y próximos pasos

En este artículo hemos visto otra posibilidad para implementar la inyección de parámetros en acciones ASP.NET MVC, utilizando esta vez model binders, dado que son éstos los encargados de crear las instancias de las clases que son suministradas como parámetros a los métodos. Como siempre, hemos podido comprobar lo sencillo que resulta modificar la implementación que viene de serie en el framework para adaptarlo a nuestras necesidades.

Sin embargo, ciertamente es bastante trabajoso, y más aún teniendo en cuenta que hay ya herramientas que nos lo dan solucionado. En el próximo post, el último de la serie, veremos una de ellas 😉

El ejemplo desarrollado en este post podéis descargarlo desde Skydrive.

Publicado en Variable not found.

Inyección de parámetros en acciones ASP.NET MVC (I)

ASP.NET MVCNo hace demasiado tiempo describíamos por aquí cómo desacoplar controladores ASP.NET MVC mediante el uso de Inyección de Dependencias. En este caso, como en otros que hemos tratado, se hacía uso de uno de los “sabores” de esta técnica, que consiste en suministrar como parámetros al constructor de una clase todos aquellos componentes de los que depende para su funcionamiento. Sin embargo, esta no es la única forma de usar inyección de dependencias; hay otros métodos, quizás menos usados, como la inyección de propiedades o la inyección de parámetros en métodos que pueden ser interesantes en algunos casos y que, como mínimo, vale la pena conocer.

El primero de ellos consiste en hacer que el componente encargado de configurar una instancia aporte de forma automática contenido para determinadas propiedades de la clase, lo cual puede ser útil en contextos en los que no se tiene acceso directo al procedimiento de creación de la instancia; imaginad, por ejemplo, si queremos inyectar dependencias en un atributo .NET, cuya instanciación se realiza fuera de nuestro alcance.

El segundo de ellos restringe el ámbito de la dependencia a un método concreto; es decir, no se pasarán dependencias a la clase como se hace en los dos casos anteriores, sino exclusivamente al método o métodos que vayan a necesitar los componentes externos usando sus parámetros, algo así:

Inyección de parámetros en métodos
La implementación de esta técnica en acciones ASP.NET MVC es la que vamos a ver a lo largo de esta serie de posts. Pero ojo, que aunque a priori pueda resultar una idea atractiva y puede encajar bien en determinados escenarios, no está exenta de inconvenientes; además de hacer menos explícitas las dependencias de una clase (con la inyección en constructor son totalmente explícitas), hacen más difícil el seguimiento de principios de diseño como SRP y se puede tender a crear componentes demasiado complejos, con código duplicado, extensos, y con múltiples responsabilidades.

En cualquier caso, es una magnífica oportunidad para profundizar en la forma en que ASP.NET MVC trabaja internamente y ver lo sencillo que es su extensión una vez conocidas las bases de su funcionamiento. Por ello, veremos distintas fórmulas para conseguir nuestros objetivos:

  • Manualmente, usando action invokers (en este post)
  • Manualmente, usando model binders
  • Automáticamente, usando Autofac, un contenedor IoC que ya ofrece de serie inyección de parámetros.

La última de las opciones es la más recomendable por su sencillez, pero sin duda las dos primeras son las que más nos pueden enseñar de los entresijos de ASP.NET MVC, así que vamos con ellas.

Inyección de parámetros usando action invokers

Simplificando un poco, cuando ASP.NET MVC debe ejecutar una acción, hay un componente interno llamado action invoker, cuya principal implementación se encuentra en la clase ControllerActionInvoker,  que se encarga de recorrer los parámetros del método y, por cada uno de ellos, crear un binder que intente proporcionar un valor o instancia para el mismo. Una vez obtenida esta información, y teniendo en cuenta otros datos del contexto como los filtros de autorización o la validación de datos de entrada, ejecutará la acción suministrando los parámetros obtenidos anteriormente, y finalmente ejecutará también el resultado devuelto.

En particular, dicha clase dispone de un método llamado GetParameterValue() que es llamado por cada parámetro definido en la acción y retorna el valor para el mismo, por lo que es un lugar ideal para introducir la lógica de inyección que pretendemos.

El action invoker usado por defecto en ASP.NET MVC 4 se encuentra en la clase AsyncControllerActionInvoker , que es una extensión de la base ControllerActionInvoker. Extendiendo esta clase podemos tomar el control en el momento de obtención de los valores e introducir la lógica que nos interese; en este caso, por simplificar, usaremos la convención de que los parámetros de tipo interfaz serán resueltos de forma automática usando el dependency resolver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InjectionControllerActionInvoker: AsyncControllerActionInvoker
{
    protected override object GetParameterValue(
                ControllerContext controllerContext,
                ParameterDescriptor parameterDescriptor)
    {
        if (parameterDescriptor.ParameterType.IsInterface)
        {
            return DependencyResolver.Current
                    .GetService(parameterDescriptor.ParameterType);
        }
        return base.GetParameterValue(
                controllerContext, parameterDescriptor);
    }
}

Como se observa, lo único que hacemos es sobrescribir el método GetParameterValue() y detectar cuándo se está intentando obtener un valor para un interfaz, retornando el resultado de intentar su resolución usando el DependencyResolver de MVC. Por supuesto, aquí podríamos haber introducido también una llamada a nuestro contenedor IoC para realizar esta resolución. Puedes leer más sobre cómo resolver dependencias usando DependencyResolver o un contenedor IoC a partir del punto cuarto de este post.

Bien, pero para que lo anterior funcione necesitamos aún hacer dos cosas: indicar al framework que el action invoker que debe utilizar es nuestra clase InjectionControllerActionInvoker, en lugar de la usada por defecto (ControllerActionInvoker), y, por supuesto, configurar el dependency resolver para que sea capaz de resolver las dependencias solicitadas.

¿Cómo establecer el nuevo action invoker?

Una forma de conseguir esto es desde el propio controlador, sobrescribiendo el método CreateActionInvoker() que heredamos de la clase Controller, por ejemplo así:

1
2
3
4
5
6
7
public class ControllerBase: Controller
{
    protected override IActionInvoker CreateActionInvoker()
    {
        return new InjectionControllerActionInvoker();
    }
}

Esto podemos hacerlo como en el ejemplo anterior, en un controlador base del que heredaríamos todos nuestros controladores, o bien, aunque tenga menos sentido, directamente sobre los controladores donde queramos que se realice la inyección de parámetros.

Otra posibilidad menos intrusiva y que no nos obliga a crear un controlador base es hacer uso del propio dependency resolver de ASP.NET MVC para resolver este componente. De hecho, si observamos el código fuente del framework, podremos observar que la implementación del método CreateActionInvoker() de la clase Controller es la siguiente:

1
2
3
4
5
6
7
protected virtual IActionInvoker CreateActionInvoker()
{
  return
    (IActionInvoker) DependencyResolverExtensions.GetService<IAsyncActionInvoker>(this.Resolver) ??
                     DependencyResolverExtensions.GetService<IActionInvoker>(this.Resolver) ??
                    (IActionInvoker) new AsyncControllerActionInvoker();
}

Es decir, antes de retornar un nuevo objeto AsyncControllerActionInvoker, el framework solicitará una instancia al dependency resolver, por lo que bastaría con sobrescribirlo o registrar este componente en el contenedor IoC que estemos usando para tenerlo solucionado. El siguiente ejemplo simplificado muestra cómo conseguirlo en escenarios simples, sin necesidad de usar un contenedor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        if (serviceType == typeof (IAsyncActionInvoker))
            return new InjectionControllerActionInvoker();
        if (serviceType == typeof(IProductServices))
            return new ProductServices();
        if (serviceType == typeof(INotificationServices))
            return new NotificationServices();
        return null;
    }
 
    public IEnumerable<object> GetServices(Type serviceType)
    {
        return Enumerable.Empty<object>();
    }
}
 
//
// In global.asax.cs, Application_Start()
//
DependencyResolver.SetResolver(new MyDependencyResolver());

Y de esta forma, nuestro código saldría andando. A grandes rasgos, la ejecución de llamada a la acción Products del controlador Home que hemos visto al principio del post seguiría estos pasos:

  • Al arrancar la aplicación, en el global.asax se establece que el dependency resolver a utilizar por la aplicación será una instancia de MyDependencyResolver.  
  • Una vez el framework ha determinado que debe ejecutar el método Products() de la clase HomeController, llamará a CreateActionInvoker() del controlador para obtener el componente que se encargará de invocarlo. 
  • La implementación por defecto de CreateActionInvoker() llama al dependency resolver solicitándole una instancia de una clase que implemente el interfaz IAsyncActionInvoker
  • Nuestro dependency resolver retorna una instancia de InjectionControllerActionInvoker
  • El framework recorre todos los parámetros de la acción a ejecutar, Products(), llamando al método GetParameterValue() del action invoker para obtener valor de cada uno de ellos. 
  • El parámetro de tipo IProductServices, al ser de tipo interfaz, lo resolvemos desde nuestra clase action invoker llamando de nuevo al dependency resolver, al que solicitamos una instancia de dicha interfaz.
  • MyDependencyResolver retorna una instancia de ProductServices.
  • Finalmente, dado que el framework ya dispone de valores para todos los parámetros, ejecuta la acción requerida.

Ah, un último comentario para que no se me olvide. Probablemente muchos os estaréis preguntando cómo podríamos gestionar con este enfoque la liberación de las dependencias al finalizar la petición, pues en el ejemplo que hemos visto estos aspectos no están contemplados.

Lo más sencillo es delegar a un contenedor la gestión del ciclo de vida de las instancias, de forma que no tengamos que preocuparnos mucho por ello, tal y como se describe en este post. Aplicando a nuestro ejemplo, esto significa que nuestro dependency resolver utilizaría un contenedor IoC como Unity para obtener las instancias, y éste sería el responsable de eliminarlas al finalizar cada petición.

Resumen y próximos pasos

En este artículo hemos visto cómo utilizar uno de los puntos de extensibilidad de MVC, los action invokers, para inyectar parámetros a acciones ASP.NET MVC. En el siguiente post mostraremos cómo conseguir exactamente lo mismo, pero utilizando esta vez model binders, que son los componentes encargados de proporcionar valores a los parámetros de las acciones partiendo de los datos asociados a la petición actual.

Podéis descargar el ejemplo completo desde mi Skydrive 🙂

Publicado en http://www.variablenotfound.com/2013/06/inyeccion-de-parametros-en-acciones.html

Control de acceso a Hubs SignalR

 

SignalR

SignalR permite controlar el acceso a los métodos del interior los Hubs de una forma muy similar a como hacemos en ASP.NET MVC o WebAPI.

Y cuando digo muy similar no estoy exagerando en absoluto, como podéis observar en el siguiente código:

1
2
3
4
5
6
7
8
[Authorize]
public class AlertService : Hub
{
    public void Alert(string msg)
    {
        this.Clients.All.showAlert(msg);
    }
}

imagePues sí, también tenemos disponible aquí el atributo [Authorize]. O mejor dicho, otro atributo [Authorize]. Cuidado con esto, porque en un proyecto MVC4 con WebAPI y SignalR tendremos a nuestra disposición tres atributos con el mismo nombre, y si seleccionamos el namespace incorrecto será ignorado por completo, dejando libre el acceso al recurso que se intenta proteger.

¿Recordáis el famoso infierno de las DLL de antaño? Pues no va a ser nada comparado con el namespace hell que se está cociendo ;-D

Bueno, la cuestión es que aplicando este atributo a un Hub de la forma mostrada anteriormente impediremos el acceso a sus métodos a clientes que no hayan sido autenticados en el sistema. Si únicamente queremos efectuar este control en métodos concretos, bastará con decorarlos con [Authorize] de forma individualizada:

1
2
3
4
5
6
7
8
9
10
public class AlertService: Hub
{
    [Authorize]
    public void Alert(string msg)
    {
        this.Clients.All.showAlert(msg);
    }
 
    // Other hub methods
}

A la hora de introducir el atributo podemos ser aún más explícitos. Así, es posible permitir el acceso a un Hub o método a uno o varios usuarios separando su nombre por comas:

1
2
3
4
5
[Authorize(Users="fmercury,mjackson,fsinatra")]
public void Sing(string msg)
{
    // ...
}

O también podemos hacerlo por roles dentro del sistema:

1
2
3
4
5
[Authorize(Roles= "greatsingers")]
public void Sing(string msg)
{
    // ...
}

Por último, comentar que a diferencia de los homónimos atributos usados en MVC y WebAPI, este nuevo Authorize dispone de un parámetro booleano adicional llamado RequireOutGoing que si establecemos a falso hará que las peticiones al método afectado sean ejecutadas sin necesidad de autenticación.

Publicado en Variable not found.

CDN con fallbacks en bundles

ASP.NETLa nueva versión de System.Web.Optimization traerá (aún está en beta) algunas novedades interesantes al sistema de bundling que se incluye de serie en los proyectos ASP.NET MVC y se distribuye a través de Nuget en el paquete Microsoft.AspNet.Web.Optimization.

En particular, vamos a centrarnos en una característica muy práctica si queremos utilizar una Content Delivery Network (CDN) externa (como la de Microsoft, Google o incluso una propia) para delegar a ella la carga de bibliotecas de script comunes, pero queremos a la vez proporcionar una alternativa local por si acaso ésta fallase debido a cualquier motivo.

1. La situación actual

El sistema de bundling ya nos permitía, al mismo tiempo que definíamos los bundles, especificar la dirección en la CDN a través de la cual el archivo podía ser obtenido. Esto lo hacíamos justo en el momento de la creación del bundle:

1
2
3
4
5
bundles.Add(new ScriptBundle(
        "~/bundles/jquery", // Bundle virtual path
    )
    .Include("~/Scripts/jquery-{version}.js"));

Este bundle podemos referenciarlo desde la vista usando la dirección virtual asignada como sigue:

1
2
3
4
      ...
      @Scripts.Render("~/bundles/jquery")
   </body>
</html>

El código resultante de esta llamada depende del valor establecido en la propiedad booleana BundleTable.Bundles.UseCdn . Si contiene false, el valor por defecto, el tag <script> generado apunta a la dirección del servidor local a través de la cual es posible descargar el archivo comprimido:

1
<script src="/bundles/jquery?v=UgyEMAYOuSB9Bb6HcOEVHpd6fIIp54yF086SRNVcdIY1"></script>

En cambio, si el valor de BundleTable.Bundles.UseCdn es true, la URL que aparecerá en el tag es la indicada como dirección del archivo en el CDN:

2. Las novedades

Lo anterior está bien pero, aunque puede resultar válido en muchos escenarios, hay casos en los que es claramente insuficiente y tenemos que hacer algunos apaños para que todo vaya bien. Sólo tenéis que imaginar que estamos desarrollando una aplicación con esas referencias en un ordenador sin internet…

Pues para evitarnos trabajo, la nueva versión de System.Web.Optimization incluye un mecanismo automático de fallback que nos permite detectar cuándo ha habido problemas descargando nuestra biblioteca desde el CDN, y, en este caso, cargar el bundle local de forma automática.

La forma de conseguirlo es idéntica a la de antes: definimos un bundle de un archivo especificando la dirección de la URL para obtenerlo desde la CDN, e indicamos en él una expresión de fallback, es decir, una expresión que será evaluada en tiempo de script y que debe determinar si la biblioteca ha sido cargada con éxito. Por ejemplo, para saber si jQuery ha sido cargado con éxito sólo hay que observar si hay un objeto en window.jQuery, ¿verdad? Pues esa sería la expresión de fallback.

El siguiente ejemplo muestra cómo deberíamos crear el bundle y añadirlo a la tabla de bundles del sistema especificando la expresión de comprobación:

1
2
3
4
5
6
7
8
var jquery = new ScriptBundle(
        "~/bundles/jquery", // Bundle virtual path
    )
    .Include("~/Scripts/jquery-{version}.js");
 
jquery.CdnFallbackExpression = "window.jQuery";
BundleTable.Bundles.Add(jquery);

Una vez referenciado en la vista, el código de marcado generado será el siguiente:

1
2
<script>(window.jQuery)||document.write('<script src="/bundles/jquery"></script>');</script>

Observad que lo único que se hace es introducir un bloque de script justo después de la referencia al CDN en el que se evalúa la expresión de fallback y, si no se cumple, se genera sobre la página una nueva referencia al bundle local. Si queréis comprobar su funcionamiento, sólo tenéis que introducir una dirección incorrecta en la URL al CDN, y veréis cómo se utiliza el archivo local.

Por último, indicar que podéis probar estas cosas descargando desde Nuget la versión prerelease del paquete. Ah, y recordad que para que el bundling funcione correctamente es necesario desactivar el modo depuración en el web.config, o bien habilitar manualmente este mecanismo introduciendo la siguiente asignación en algún punto de la inicialización de la aplicación:

1
BundleTable.EnableOptimizations = true;

En definitiva, es algo que ya podíamos solucionar desde la primera versión de forma manual, pero que ahora lo tendremos más fácil al venir integrado en el producto 🙂

Publicado en Variable not found.

Crear manualmente proxies de Hubs Signalr

SignalRCuando desde un cliente javascript consumimos los servicios suministrados por un Hub de SignalR, lo habitual es usemos los proxies generados automáticamente, para lo que solemos incluir en nuestra página una referencia al script “/Signalr/Hubs” según la ruta por defecto.

Sin embargo, puede haber casos en los que no nos interesa este comportamiento y preferimos generarlo de forma manual para, por ejemplo, incluirlo en un bundle o distribuir el archivo a través de una CDN. Veamos cómo conseguirlo.

Los desarrolladores de SignalR han creado una herramienta de línea de comandos que, entre otras cosas, nos facilitará mucho esta tarea, por lo que lo primero que tenemos que hacer es descargarla e instalarla a través de Nuget:

PM> Install-Package microsoft.aspnet.signalr.utils 
[...]
Successfully installed 'Microsoft.AspNet.SignalR.Utils 1.0.1'.

Por supuesto, este primer paso podrías hacerlo también usando el interfaz gráfico, pero mejor que lo hagas ya desde la consola Nuget porque de todas formas nos va a hacer falta en el siguiente paso.

A continuación hay que ejecutar en la consola el siguiente comando:

PM> signalr.exe ghp /o:MyProjectscriptshubs.js
SignalR Utility Version: 1.0.0.0
Creating temp directory 
  C:UsersJmAguilarAppDataLocalTemp532fbeb3-55c9-44cb-ac94-3f831318ce89

Incluir script en el proyectoComo seguro podéis intuir, el parámetro /o permite indicar la ruta hacia el archivo que será generado. Es relativa a la solución, por esta razón hay que comenzarla desde la carpeta donde se encuentra el proyecto.

El parámetro “ghp” simplemente viene de “Generate Hub Proxies”.

Al finalizar el proceso tendremos en la carpeta indicada el archivo, cuyo contenido será idéntico al obtenido al descargar desde la vista el script generado en /Signalr/Hubs. Eso sí, no aparecerá en Visual Studio hasta que lo incorporemos explícitamente al proyecto.

Hecho esto, ya podemos referenciarlo desde nuestras páginas:

1
<script src="Scripts/hubs.js"></script>

Podemos también introducir la generación de proxies en el proceso de build, asegurando así que el script siempre estará sincronizado con el resto de componentes. La única precaución a tener en cuenta es que la ruta del archivo generado, indicada en el parámetro /o, debe ser absoluta.

Por último, comentar que también es posible desactivar del servidor SignalR la generación de proxies, lo cual puede ser interesante si nuestros clientes estamos seguros de que no van a necesitarlo (por ejemplo, si tenemos sólo clientes .NET, o si hemos pregenerado los archivos de script), para evitar que usuarios del lado oscuro puedan obtener demasiada información sobre los Hubs y operaciones disponibles en servidor. En este caso, basta con indicar esta configuración en el momento de mapear los hubs, justo al arrancar la aplicación:

1
2
3
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs(hubConfiguration);

A partir de este momento, el script descargado a través de la dirección Signalr/hubs simplemente lanzará una excepción indicando que esta característica ha sido deshabilitada.

Publicado en Variable not found.

Evento en Sevilla: desarrollo de videojuegos multiplataforma con Wave Engine

Wave EngineSin duda, el desarrollo de videojuegos es una de las áreas más atractivas e interesantes a las que podemos aspirar dedicarnos los desarrolladores. Y aunque hoy en día parece estar especialmente de moda, no es algo nuevo; muchos de nosotros empezamos a interesarnos por la programación intentando programar nuestros propios juegos.

Por este motivo espero con especial impaciencia la próxima charla organizada por Cartuja.NET, que tratará sobre el desarrollo de videojuegos, pero con con un enfoque muy alineado con las necesidades actuales: crear videojuegos multiplataforma, usando para ello el motor Wave Engine.

Será el próximo jueves 23 de mayo a las 19:30h, con una duración estimada de dos horas, en el Clouding Point/Centro demostrador TIC de Sevilla, que podéis encontrar en la siguiente dirección:

C/Biología, 12, Edificio Vilamar 2, 3ª Planta
Parque Empresarial Nuevo Torneo
41015 Sevilla

Como siempre, la asistencia es gratuita; lo único que tenéis que hacer es registraros en el sitio del evento en Eventbrite 🙂

Descripción del evento

El desarrollo de aplicaciones para dispositivos móviles es un área que gana adeptos y suma peso día a día. Entre las aplicaciones destacadas, más descargadas y que aportan grandes beneficios contamos con los juegos. Dada la variedad de dispositivos, SDKs, herramientas y lenguajes a aprender, sacar el máximo partido a cada plataforma con eficacia y rapidez es una tarea complicada.  Wave Engine es un motor multiplataforma en 3D para facilitar la adaptación de los juegos móviles a cualquier plataforma (Android, iOS, Windows Phone y Windows 8). Incluye multitud de herramientas que facilitan tareas importantes como la gestión de publicidad, analítica del juego, etc.

En este evento se realizará una introducción al engine donde se mostrarán todas sus  posibilidades.

Ponentes

  • Marcos Cobeña (LIGHTYEAR): Developer Advisor en Plain Concepts.
  • David Ávila (WOODY): Software Developer Advisor Plain Concepts.

Salvo catástrofe bíblica, u otros impedimientos de fuerza mayor, nos vemos por allí 🙂

Generar archivos Excel como un señor con ClosedXml

 

Anónimo
Venga, lo confieso: yo también he generado desde mis aplicaciones contenidos HTML y los he enviado al cliente en un archivo con extensión XLS, incluso modificando el content-type, para que pareciera un documento de hoja de cálculo. Durante años. Y también le he dicho a mis clientes que el molesto mensaje que aparece al abrirlo desde Excel, el que indica que el contenido del archivo no coincide con la extensión del mismo, es algo normal.

Pero esto se acabó desde que descubrí ClosedXML, un magnífico componente para .NET basado en el estándar OpenXML que permite la generación de archivos Excel “de verdad”, con formato, estilos, fórmulas, rangos, filtros, y casi todo lo que se nos pueda ocurrir.

ClosedXMLClosedXML, proyecto iniciado por Manuel de León y distribuido bajo licencia MIT, se aleja de la verbosidad y amplitud de alcance del Open XML SDK de Microsoft, ofreciendo un API mucho más natural e intuitivo exclusivamente diseñado para crear y manipular documentos Excel. De hecho, el nombre ClosedXML lo eligió después de conocer el SDK oficial y pensar “si es así como se trabaja con Open XML, preferiría utilizar algo que estuviera cerrado”, en referencia a la complejidad que el primero supone.

Dado que se basa en Open XML, para abrir los archivos generados se necesita Excel 2007 o una versión posterior, aunque creo que ocho años después de su aparición ya podríamos considerar que es un mínimo bastante razonable 😉

Instalación del componente

Como de costumbre, la instalación de ClosedXML la vamos a realizar a través de Nuget:

PM> Install-Package ClosedXML
Attempting to resolve dependency 'DocumentFormat.OpenXml (≥ 1.0)'.
Successfully installed 'ClosedXML 0.68.1'.
Successfully added 'DocumentFormat.OpenXml 1.0' to ClosedXmlDemo.Model.
Successfully added 'ClosedXML 0.68.1' to ClosedXmlDemo.Model.

No me canso de repetirlo: Nuget, ¿dónde has estado todos estos años? 😉

Creación y salvado de un documento Excel básico (Desktop, Webforms, MVC)

La creación y salvado a disco de un documento Excel es absolutamente trivial. Basta con instanciar un objeto de la clase XLWorkBook, añadir una nueva hoja su colección de Worksheets, e introducir valores en ella a través de su propiedad Cell, como podemos observar en el código genérico mostrado a continuación:

1
2
3
4
var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sheet 1");
worksheet.Cell(1, 1).Value = "Hello, world!";
workbook.SaveAs("c:\temp\excel.xlsx");

De esta forma tan simple, en c:tempexcel.xlsx tendremos lo siguiente:
Hoja de cálculo generada con ClosedXML
Si estamos desarrollando una aplicación web, cuando generamos un archivo Excel lo habitual es que lo enviemos al usuario como documento adjunto para que lo descargue y guarde en su equipo, lo que implica modificar el content-type y añadir un encabezado content-disposition.

En este caso de usar WebForms, el código a emplear es, poco más o menos, el siguiente:

1
2
3
4
5
6
7
8
9
10
11
Response.Clear();
Response.ContentType =
     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment;filename="Demo.xlsx"");
 
using (var memoryStream = new MemoryStream())
{
    workbook.SaveAs(memoryStream);
    memoryStream.WriteTo(Response.OutputStream);
}
Response.End();

Observad que no estamos salvando el workbook  sobre un MemoryStream, que luego volcamos al stream de salida para que viaje al cliente. Este doble paso, en cualquier caso bastante sencillo de implementar, se debe a que ClosedXML requiere para guardar el archivo que el stream de salida sea de avance y retroceso, y es algo que OutputStream no cumple.

En caso de tratarse de ASP.NET MVC, el código es prácticamente el mismo, aunque lo correcto sería implementarlo en un ActionResult personalizado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ExcelResult: ActionResult
{
    private readonly XLWorkbook _workbook;
    private readonly string _fileName;
 
    public ExcelResult(XLWorkbook workbook, string fileName)
    {
        _workbook = workbook;
        _fileName = fileName;
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.Clear();
        response.ContentType = "application/vnd.openxmlformats-officedocument."
                             + "spreadsheetml.sheet";
        response.AddHeader("content-disposition",
                           "attachment;filename=""+ _fileName +".xlsx"");
 
        using (var memoryStream = new MemoryStream())
        {
            _workbook.SaveAs(memoryStream);
            memoryStream.WriteTo(response.OutputStream);
        }
        response.End();
    }
}

Y ya podríamos usarlo directamente desde un controlador:

1
2
3
4
5
6
7
8
public ActionResult GenerateExcel()
{
    // Generate the workbook...
    var workbook = ClosedXmlDemoGenerator.GenerateWorkBook();
 
    // ... and return it to the client
    return new ExcelResult(workbook, "demo");

Establecer valores en las celdas

Las celdas podemos referenciarlas utilizando la propiedad Cell de los objetos IXLWorksheet, y podemos hacerlo indicando su número de fila y columna o mediante el nombre usado normalmente en el mismo Excel:

1
2
worksheet.Cell(1, 1).Value = "Hello, world!";
worksheet.Cell("A2").Value = "How are you?";

Establecer valores de celdas con ClosedXML
Por supuesto, los valores pueden ser de todo tipo, de hecho la propiedad Value que establecemos es de tipo object, aunque obviamente sólo serán reconocidos los tipos habituales de Excel:

1
2
3
4
5
6
7
8
9
10
11
12
worksheet.Cell("A2").Value = "Text";
worksheet.Cell("B2").Value = "Hi!!";
worksheet.Cell("A3").Value = "Integer";
worksheet.Cell("B3").Value = 3;
worksheet.Cell("A4").Value = "Decimal";
worksheet.Cell("B4").Value = 3.5;
worksheet.Cell("A5").Value = "Boolean";
worksheet.Cell("B5").Value = true;
worksheet.Cell("A6").Value = "DateTime";
worksheet.Cell("B6").Value = DateTime.Now;
worksheet.Cell("A7").Value = "Object";
worksheet.Cell("B7").Value = new InvoiceDetails();

Tipos de datos en ClosedXML
También podemos crear rangos y establecerles valores de forma directa:

1
worksheet.Range("A1:D5").Value = "Hi!";

Valores de rangos en ClosedXML
E incluso podemos asignar directamente colecciones de datos como objetos de tipo DataTable o IEnumerable<T>:

1
2
3
4
5
6
worksheet.Cell("A1").Value = new[]
             {
                  new { Id=1, Name="John", Age = 42},
                  new { Id=2, Name="Peter", Age = 23},
                  new { Id=3, Name="Mary", Age = 32},
             };

Uso de conjuntos de datos en ClosedXML

Formato de celdas

ClosedXML pone a nuestra disposición un rico conjunto de propiedades y métodos para dar formato a las celdas o a rangos de ellas. La sintaxis fluida que podemos utilizar facilita mucho el descubrimiento de las posibilidades de formateo, y la implementación de un código muy limpio y comprensible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
worksheet.Range("B2:E5")
         .SetValue("Hi!")
         .Style.Border.SetOutsideBorder(XLBorderStyleValues.Dotted);
 
worksheet.Range("B3:E5").Style
         .Font.SetFontSize(10)
         .Font.SetFontColor(XLColor.Gray)
         .Font.SetItalic(true);
 
worksheet.Range("B2:E2").Style
         .Font.SetFontSize(13)
         .Font.SetBold(true)
         .Font.SetFontColor(XLColor.White)
         .Fill.SetBackgroundColor(XLColor.Gray);

Formateo de celdas con ClosedXML
También podemos, por supuesto, establecer la alineación de celdas o rangos, unir celdas, o establecer el formato de visualización de sus valores. Vemos también, de paso, una forma más fluida de establecer los valores, usando SetValue():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
worksheet.Cell("A1")
         .SetValue(1234.56789)
         .Style.NumberFormat.SetFormat("#,##0.#0");
 
worksheet.Cell("A2")
         .SetValue(DateTime.Now)
         .Style.DateFormat.SetFormat("dd-mm-yyyy")
               .Alignment.SetVertical(XLAlignmentVerticalValues.Center);
 
worksheet.Range("A3:B4")
         .Merge()
         .SetValue("Merged cells")
         .Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center)
               .Alignment.TextRotation = 15; // Degrees

Formateo de celdas con ClosedXML

Uso de fórmulas

Esto es especialmente interesante, pues no había forma de conseguirlo cuando exportábamos archivos HTML, CSV o similares, en los que sólo importaba el valor de las celdas. Con ClosedXML tenemos total libertad para introducir fórmulas en las celdas; de esta forma, no sólo estaremos enviando al usuario un conjunto estático de datos, sino una completa hoja Excel que puede modificar (si nos interesa, claro) y usar para su trabajo:

1
2
3
4
5
6
7
8
9
10
11
12
13
worksheet.Cell("B2")
    .SetValue("Number")
    .Style.Font.SetBold(true);
 
for (int i = 1; i <= 5; i++)
{
    worksheet.Cell(3+i, 2).Value = i;
}
 
worksheet.Cell("A9").SetValue("Total");
worksheet.Cell("B9")
    .SetFormulaA1("=SUM(B3:B8)")
    .Style.Border.SetTopBorder(XLBorderStyleValues.Medium);

Fórmulas en ClosedXML

Otras características interesantes

ClosedXML soporta muchísimas funcionalidades adicionales a las descritas hasta el momento. Voy a citar algunas más que me han llamado la atención.

Por ejemplo, tenemos la posibilidad de añadir filtros y ordenación por columna a los datos, de manera que el usuario pueda realizar una selección de la información recibida:

1
2
3
4
5
6
7
8
9
10
11
worksheet.Cell("A1").Value = "Id";
worksheet.Cell("B1").Value = "Name";
worksheet.Cell("C1").Value = "Age";
worksheet.Cell("A2").Value = new[]
     {
          new { Id=1, Name="John", Age = 42},
          new { Id=2, Name="Peter", Age = 23},
          new { Id=3, Name="Mary", Age = 32},
          new { Id=4, Name="John", Age = 45},
     };
worksheet.RangeUsed().SetAutoFilter();

Filtros de datos con ClosedXML
Otro aspecto que puede ser interesante es la protección de celdas para que el usuario no pueda modificar sus valores. En el siguiente ejemplo, se protege la hoja completa con un password, de forma que no podrá ser editada, excepto las tres primeras columnas de la primera fila, que el usuario podrá editar con total libertad:

1
2
3
worksheet.Protect("1234"); // Locks the worksheet and sets the password
worksheet.Range("A1:C1")
    .Style.Protection.SetLocked(false); // Unlocks the range

Protección de hojas con ClosedXML
También, si nos interesa que el usuario edite valores de las celdas, es posible especificar restricciones en los datos de entrada, como su tipo, rango de valores permitidos, selección de valores desde desde un desplegable, etc.:

1
2
3
4
5
6
7
8
9
worksheet.Cell("A1").Value = "Digit 0-9:";
worksheet.Cell("B1").DataValidation.WholeNumber.Between(0, 9);
 
worksheet.Cell("A2").Value = "Two:";
worksheet.Cell("B2").DataValidation.WholeNumber.EqualTo(2);
 
worksheet.Cell("A3").Value = "Date:";
worksheet.Cell("B3").DataValidation.AllowedValues = XLAllowedValues.Date; // Only dates
worksheet.Cell("B3").DataValidation.ErrorMessage = "Only dates, please";

Validación de datos de entrada con ClosedXML
Incluso podemos añadir comentarios a celdas:

1
2
3
4
5
6
7
8
worksheet.Cell("B2").SetValue("TOTAL:")
    .Style.Font.SetBold(true)
          .Alignment.SetHorizontal(XLAlignmentHorizontalValues.Right);
 
worksheet.Cell("C2").Style.NumberFormat.SetFormat("#,##0.#0");
worksheet.Cell("C2").SetValue(9876543.21)
    .Comment.SetAuthor("jmaguilar")
    .AddText("Danger: this number seems odd!");

Añadir comentarios con ClosedXML

… y vamos a dejarlo aquí 😉

Bueno, pues tras este largo post espero que más o menos os haya quedado claro el uso y posibilidades de este magnífico componente, y que os hayan entrado muchas ganas de probarlo. Seguro cambiará la forma en que generáis los archivos Excel desde vuestras aplicaciones y abrirá nuevas posibilidades, hasta ahora difícilmente implementables usando otras alternativas.

También, recomendaros que no dejéis de leer la documentación, que es bastante extensa y detallada, donde podréis ver muchas más características que no he comentado para no hacer un post más interminable de lo que ya es 😉

Podéis descargar un proyecto de prueba desde mi Skydrive, donde veréis en funcionamiento la generación de archivos Excel desde una aplicación de consola, ASP.NET Webforms, y MVC.

Publicado en Variable not found.