lunes, 28 de junio de 2010

Parámetros de cualquier tipo en métodos de RIA services

Bueno, pues resulta que estaba yo tan feliz y contento escribiendo unos métodos para un servicio RIA, cuando me he encontrado con un problemilla inesperado. Digamos que yo había implementado un método tal que este:

En el cliente, el método debe ser invocado así:

donde ItemsFilter es una clase que contiene un puñado de criterios de filtrado para los datos que deben ser devueltos. Todo muy bonito y rebosante de felicidad.

Peeeero... resulta que tal código no compila. Da un error como este: Operation named 'GetItems' does not conform to the required signature. Parameter types must be an entity type or one of the predefined serializable types.

¿Y dónde está el problema? Pues en este foro de MSDN, donde la gente se caga en muestra un profundo malestar con el equipo de desarrollo de Microsoft, está explicado ferpectamente: No se pueden pasar tipos arbitrarios como parámetros a los métodos de un servicio RIA, sólo unos cuantos tipos predefinidos (mayormente, cadenas y números).

Por suerte, esto tiene una solución más o menos sencilla: se trata de cambiar el parámetro conflictivo para que sea una cadena en vez de ser del tipo deseado. En el cliente se serializa en XML el objeto en cuestión, en el servidor se deserializa, y todos contentos. Esto funcionará si la clase del parámetro se puede serializar en XML, cosa que para objetos sencillos de transferencia de datos normalmente será cierta.

¿Y cómo conseguimos tal cosa? Pues si quieres saber cómo lo he hecho yo, sigue leyendo.

En primer lugar he creado en el proyecto de servidor una clase auxiliar con métodos estáticos para serializar y deserializar objetos en XML. He llamado al fichero SerializationHelper.shared.cs, de esta forma lo tengo disponible también en el cliente. Este es el código de la clase:

A continuación he modificado el código del método en el servicio para que admita una cadena como parámetro, a partir de la cual se reconstruye el objeto:

Y en cuanto al cliente, el código de invocación es ahora este:

...y con este un tanto incómodo pero útil truquillo, ya podemos seguir RIAcizando felices y contentos. La pregunta es: ¿por qué no hace esto ya el compilador? ¿Qué llevó a los diseñadores del entorno RIA a imponer una limitación tan tonta? Misterio misterioso.

jueves, 24 de junio de 2010

WCF RIA services + inyección de dependencias

Tengo que confesarlo: soy un fan del patrón inyección de dependencias. Y también me gusta bastante la tecnología WCF RIA services, que estoy usando ahora en un proyecto. Problemilla: ambas cosas no casan muy bien a priori, puesto que a no ser que se indique lo contrario, las clases que proporcionan los servicios RIA (las clases derivadas de DomainContext) son instanciadas automáticamente por el motor de ASP.NET, asumiento que tienen un constructor sin parámetros. Pero claro, la inyección de dependencias se consigue precisamente pasando las dependencias como parámetros del constructor (hay otras formas, pero tampoco casan muy bien con la creación automática de la clase).

Bueno, ¿y cómo "se indica lo contrario"? La solución está explicada en esta entrada de StackOverflow: básciamente se trata de decirle a la clase DomainService que use una factoría personalizada cada vez que necesite una instancia del servicio, cosa que se hace desde el constructor estático se la clase Global de ASP.NET. Esa factoría personalizada es una clase que proporcionamos nosotros y desde la que podemos crear la instancia del servicio a nuestro gusto.

Veamos pues cómo extender esta idea para usar inyección de dependencias en nuestros servicios RIA.

El proyecto

He creado un solución de Visual Studio sencilla de ejemplo para ilustrar el concepto. Se compone de dos proyectos Silverlight (la aplicación principal y una biblioteca de clases para el acceso a los servicios), más un proyecto web (que aloja la aplicación Silverlight) y un proyecto de bliblioteca de clases que aloja el servicio (derivado de DomainService). La idea es usar inyección de dependencias para suministrar una capa de acceso a datos a la clase que implementa el servicio.

La solución tiene tal que este aspecto:


En enlace RIA está establecido entre el proyecto Client.Services de la parte cliente y el proyecto Services de la parte servidor, quedando pues las dependencias entre proyectos tal que así:


Además de las dependencias entre proyectos, es necesario añadir un par de referencias más al proyecto MyRiaApp.Web:
  • System.ServiceModel.DomainServices.Server.dll, necesario para poder acceder a la clase DomainService.

  • Microsoft.Practices.Unity.dll, se trata de Unity, el inyector de dependencias de Microsot, que es por cierto el que se usa en el proyecto.
El proyecto MyRiaApp.Services expone un servicio RIA llamado MyRiaDomainService que contiene métodos expuestos al cliente por medio del enlace RIA. Para simplificar, en este proyecto de ejemplo sólo se expone un método, que devuelve un entero con un contador de elementos que supuestamente son objetos útiles para la aplicación. Este es el código del servicio:
Tal como he mencionado al principio, el servicio usa una capa de datos para aislar el acceso al motor de persistencia (rimbombante nombre para lo que es la base de datos), representado por un interfaz llamado IDataAccessLayer cuyo código es este:

Un posible esqueleto de implementación de IDataAccessLayer podría ser este:

Estamos usando el mecanismo de inyección de dependencias con la clase MyRiaDomainService, a la que pasamos una instancia de IDataAccessLayer en el constructor; y también con la clase IDataAccessLayer, a la que pasamos la cadena de conexión. Ahora se trata de hacer que ASP.NET use el inyector de dependencias (Unity, en este caso) para resolver las idems a la hora de crear instancias del servicio.

Inyectando

Para conseguir tal cosa, añadiremos al proyecto web una clase Global.asax en la que pegaremos el siguiente código (resalto las partes más interesantes):

¿Qué hemos hecho aquí? Pues un par de cosas:
  • Le hemos dicho a ASP.NET que use la clase MyAppDomainServiceFactory cada vez que necesite crear una instancia de un servicio RIA.

  • Hemos creado una instancia de UnityContainer, que queda disponible para toda la aplicación por medio de Global.DependencyInjector.

  • Hemos creado una instancia de DataAccessLayer, que hemos registrado en el inyector de dependencias.

  • Hemos configurado MyAppDomainServiceFactory para que obtenga las instancias de los servicios a partir del inyector de dependencias.
De esta forma, cada vez que se requiera una instancia de un servicio, será el inyector de dependencias quien lo proporcione, encargándose de resolver cualquier dependencia existente (la dependencia de IDataAccessLayer en MyRiaDomainService, en este caso.)

Nótese que una ventaja adicional de este sistema es que funcionará incluso con servicios que no tengan ninguna dependencia. Por ejemplo, en paralelo a MyRiaDomainService tenemos un AuthenticationDomainService que sólo tiene un constructor sin parámetros. Unity será capaz de crear una instancia de dicho servicio sin problemas, simplemente no le inyectará ninguna dependencia.

Un apunte más. Estamos obteniendo la cadena de conexión para la capa de datos a partir del archivo de configuración Web.config (estamos haciendo una "inyección de dependencias manual" en este caso). Pego aquí el contenido de dicho archivo (la cadena de conexión es un ejemplo, claro), que de paso muestra los elementos necesarios para que funcionen los servicios RIA:

Conclusión

El patrón inyección de dependencias ayuda a conseguir código legible y mantenible. Ahora ya sabes cómo usarlo en conjunción con los servicios RIA, lo cual sin duda incrementará tu felicidad en varios enteros, o no.

jueves, 17 de junio de 2010

Twiteando desde el MSX en japonés... o casi

Como ya dije en mi blog principal, hace poco he desarrollado un cliente de Twitter para MSX llamado MSX trivial tweeter. Una de las principales carencias del mismo era la imposibilidad de escribir en japonés, dada la complejidad de convertir el juego de carácteres SHIFT-JIS a UTF8.

Pues bien, al final me he puesto a ello y he implementado soporte para escribir en japonés desde MSX trivial tweeter.




La conversión del juego de caracteres la hago mediante un fichero de 64K que contiene la conversión a UTF16 de todos los posibles caracteres del SHIFT-JIS (almaceno en UTF16 y convierto a UTF8 en tiempo real; si no, el fichero debería ser de 128K). Hay mucho espacio vacío en el fichero y se podría reducir bastante su tamaño usando algún método de compresión, pero de momento no me voy a complicar más la vida con este asunto.

Bueno, espero que no hayais descorchado el champán todavía porque ahora viene la parte chunga. Resulta que tanto InterNestor Lite como DenYoNet se dan de sopapos con el modo Kanji de los MSX (de los Turbo-R al menos). La captura que habéis visto más arriba está hecha con un emulador en modo MSX2+, y ya con ese entorno empiezan las cosas raras: tengo que activar el modo Kanji (CALL KANJI desde BASIC) antes de instalar el InterNestor, si no, lo único que conseguimos es un precioso cuelgue.

Pero en Turbo-R (tanto emulado como real), no hay manera. Ni antes ni después: el modo Kanji y cualquiera de los softwares TCP/IPeros que he desarrollado no pueden funcionar juntos de ninguna de las maneras.

¿Y esto por qué pasa? Pues parece ser que por culpa de la forma que tiene la Kanji ROM del Turbo-R de gestionar la BIOS extendida, y no soy el primero que tiene este problema (por lo visto el MEMMAN, conocido y veterano gestor de memoria desarrollado por obsoletos holandeses, tampoco funciona con el modo Kanji activado). El código de dicha ROM asume, al instalarse, que el contenido previo del gancho de la BIOS extendida es un salto interslot (de esos tan monos que usan una instrucción RST30h). Pero claro, esto no tiene por qué ser así: en un gancho puedes poner lo que quieras, incluso una simple instrucción JP; y de hecho eso es lo que hace el InterNestor, y apostaría la melena a que el MEMMAN hace lo mismo.

¿Que no me creéis? Pues fijaos en el código desensamblado de la Kanji ROM, que obtuve hace tiempo por medio de canales oscuros y truculentos (Persicop, si me lees, tenemos que vernos un día para montar un concurso de calvas). Esta parte del código es la que inicializa el gancho de la BIOS extendida:

Fijaos que para empezar, este código mete datos en el inicio de SLTWRK, así a saco. Es decir, en vez de averiguar primero cuál es su número de slot y a partir de ahí calcular cuál es la zona de SLTWRK que le toca, pues no, usa directamente la zona destinada al slot 0-0. ¡Con dos cojones arrojo y valentía!

Además, no se guarda el primer byte del contenido previo del gancho. Se guardan el segundo, tercero y cuarto, asumiendo que contienen un número de slot y una dirección de memoria. Y claro, siguiendo esta lógica, cuando toca llamar al gancho antiguo, pues esto es lo que hace:

Toma castaña pilonga. Naturalmente, si se te ocurre poner un gancho que NO sea un salto interslot, pues tienes un cuelgue monumental en cuanto intentes usar el modo Kanji.

Aún hay cosas que no me quedan claras. Por ejemplo no sé por qué no funciona con la DenYoNet, que modifica el gancho de la BIOS extendida pero poniendo un salto interslot en principio "legal". Sea como fuere, esto es un putadón serio y molesto inconveniente.

Entonces, ¿es imposible twitear en japonés desde un Turbo-R? Bueno, se puede, dando un rodeo. La estrategia a seguir sería:
  1. Activar el modo Kanji y volcar el mensaje a enviar en un fichero de texto:


  2. Resetar el MSX e instalar el InterNestor (o no, si usas DenYoNet).

  3. SIN activar el modo Kanji, enviar el mensaje aprovechando la capacidad del MSX trivial tweeter para leer la salida de otro programa:



En efecto, un soberano coñazo un procedimiento ciertamente engorroso. No creo que a nadie le den muchas ganas de usar el programa en estas condiciones.

En fin, cuando pueda ya investigaré a ver si el problemilla este se puede solucionar. Mientras tanto habrá que olvidarse de twitear (fluidamente al menos) en japonés desde un MSX (Turbo-R al menos, no estoy seguro de si pasa en más modelos).

martes, 15 de junio de 2010

Pseudo-enumeraciones con valores de cadena en C#

El lenguaje C# nos permite definir enumeraciones para dotar de un nombre significativo a una serie de valores numéricos. En ocasiones nos encontramos con que nos iría bien poder definir enumeraciones cuyos valores no sean numéricos (típicamente cadenas), y en ese caso podemos definir constantes, pero por desgracia las constantes se definen a nivel de clase y por tanto no son reutilizables.

La buena noticia es que podemos crear algo parecido a enumeraciones con tipos no numéricos sin más que definir una clase con campos estáticos de sólo lectura. Verbigracia:

Lo malo es que perdemos todas las capacidades de manipulación de claves y valores que nos ofrece la clase Enum, pero si realmente nos hace falta tal cosa, podemos montarnos a mano los métodos que necesitemos usando un poco de reflexión (ejemplo para .NET 3.5 o superior, que como soy muy vago uso LINQ):

Con unas modificaciones mínimas, esto sirve para enumeraciones de cualquier tipo, no sólo de cadenas. O también se podría modificar la clase StringEnum para que sea genérica, cosa que dejo como ejercicio para el (hipotético) lector.

viernes, 11 de junio de 2010

Inicialización de WebContext en un proyecto de RIA services

Cuando usamos la autenticación integrada en un proyecto de RIA Services mediante la inclusión de una clase de tipo Authentication Domain Service al proyecto de servidor, podemos acceder a los servicios de autenticación y al usuario actual desde la aplicación Silverlight usando la clase WebContextBase. Si nuestra solución es del tipo "Aplicación de RIA services", Visual Studio genera automáticamente una clase llamada WebContext que nos facilita el acceso a WebContextBase. Al inicio de la aplicación tenemos que añadir una instancia de WebContext a la lista de objetos persistentes de la aplicación, lo cual puede hacerse en el constructor de App.xaml.cs tal que así (suponiendo que usamos autenticación por formularios):

A partir de ese momento podemos acceder a los servicios de autenticación mediante WebContext.Current.

Peeeero, si en vez de una aplicación RIA estamos usando una biblioteca de clases RIA, la cosa se complica un pelín, aunque no mucho. Para empezar, tenemos que crear la clase WebContext a mano, lo cual no tiene mucho misterio:

Además, en el código de inicialización hay que añadir una línea más, que crea una instancia explícita de la clase DataContext correspondiente (suponiendo que el servicio se llama AuthenticationDomainService):

Hala, ya podeis RIAcizar tranquilos incluso desde bibliotecas de clases.