lunes, 8 de febrero de 2010

NoSQL… ¿puede ser lo que necesitas?

Ultimamente se oye hablar cada vez más de BBDD no relacionales o tal y como se las conoce ahora “NoSQL”. En dosideas publicaron un interesante post al respecto de los sistemas NoSQL. La idea es renunciar a algunos de los principios (y funcionalidades) de las bases de datos tradicionales (relacionales) a cambio de obtener mayores velocidades en el acceso a datos.

Cuando nos adentramos en este mundo, debemos dejar de pensar en tablas, ya que nuestros datos dejarán de estar guardados en formato relacional. Aunque existen varios formatos en los cuales se guardan nuesteos datos parece ser que los más comunes son (clave,valor) o usar documentos que son en cierto modo una extensión de la (clave, valor). Si os pasáis por el artículo de la wikipedia sobre NoSQL hay varios enlaces a distintos sistemas NoSQL. A mi me gustaría hablaros de uno con el que he hecho algunas pruebas: MongoDB.

Montando el entorno…

Para empezar a usar el entorno, basta con descargarnos los binarios. La versión más reciente estable es la 1.2.2. MongoDB usa el esquema de numeración de versiones par, donde las versiones estables siempre son pares y las de desarrollo son impares (así actualmente en desarrollo ya existe la 1.3, que cuando se estabilice pasará a ser 1.4). Para instalar MongoDB basta con descomprimir el zip donde más os plazca :)

MongoDB está escrita en C++ y viene con una librería (.lib) y varios headers para ser usada directamente. Por suerte existe una API C# para MongoDB que os podéis descargar desde http://github.com/samus/mongodb-csharp (podéis descargaros los binarios (MongoDB.Linq.dll y MongoDB.Driver.dll) o bien el código fuente (una solución VS2008 que genera los dos assemblies mencionados).

Una vez tengáis instalado MongoDB y los dos assemblies del driver para C#… estamos listos para empezar!

Para poner en marcha el servidor de MongoDB basta con ir donde hayáis descomprimido MongoDB y lanzar el comando:

mongod --dbpath <data_path>

donde <data_path> es el directorio de datos que quereis usar.

El concepto de documentos…

MongoDB se define como base de datos de documentos, entendiendo como un documento una estructura de datos que es una colección de elementos “clave, valor”, donde las claves son cadenas y los elementos cualquier cosa que se quiera. Aunque esto puede parecerse una tabla (donde las claves sean los nombres de los campos) se diferencia del concepto de tabla en que por un lado no tiene esquema fijo (una clave puede o no aparecer en un documento) y en que los valores van más allá de los admitidos generalmente por los campos de las bases de datos relacionales (p.ej. podemos guardar colecciones de otros documentos como valores). El hecho que no haya esquema fijo hace estas bases de datos NoSql ideales para el desarrollo de soluciones que manejan datos poco estructurados.

Algunas operaciones básicas…

Para conectarnos a la BBDD basta con instanciar un objeto del tipo Mongo y llamar al método Connect. Una vez hayamos finalizado debemos llamar a Disconnect:

var srv = new Mongo();
srv.Connect();
// Operaciones con MongoDB
srv.Disconnect();





Una vez estamos conectados al servidor debemos escojer la base de datos a utilizar. Esto lo podemos hacer con el método getDB:




var db = srv.getDB("MyAppDB");





Una vez tenemos la base de datos ya podemos operar con ella. Lo que en una base de datos relacional son tablas con registros aquí son colecciones con documentos. A diferencia de una tabla relacional una MISMA colección puede tener documentos con distinto esquema (distintas claves):




// Obtenemos la coleccion 'users'
var iusers = db.GetCollection("users");
// Creamos un usuario con login y pwd
Document user = new Document();
user.Add("login", "edu");
user.Add("pwd", "mypassword");
// Insertamos el documento
iusers.Insert(user);
// Creamos otro documento. Este con login y pwd_hash
user = new Document();
user.Add("login", "edu2");
user.Add("pwd_hash", "tH23H13");
// Insertamos el documento en la MISMA colección
iusers.Insert(user);
// Obtenemos todos los elementos de la colección
var allUsers = iusers.FindAll();
int numUsers = allUsers.Documents.Count();





Un ejemplo



Vamos a ver un ejemplo de uso de MongoDB… Ahora que Lluís nos está haciendo una clase maestra sobre el membership provider, vamos a ver como podríamos implementar nuestro membership provider para que vaya contra MongoDB en lugar de contra una base de datos relacional. No voy a mostrar todo el código, sólo un par de extractos pero os dejo al final del post el enlace en formato zip con la solución de visual studio.











El primer paso es definir que base de datos de MongoDB vamos a utilizar. No me he calentado mucho la cabeza: los membership providers tienen una propiedad ApplicationName que está pensada para eso. La idea es que un mismo proveedor puede manejar datos de distintas aplicaciones. El campo ApplicationName permite saber cual es la aplicación que se está manejando. Yo asumo que la BBDD de MongoDb se llamará igual que la aplicación:




var srv = new Mongo();
srv.Connect();
var db = srv.getDB(this.ApplicationName);





Otro punto importante es no olvidarnos de llamar a Disconnect() cuando hemos terminado de trabajar con Mongo. La mejor manera de hacer esto, dado que la clase Mongo no implementa IDisposable es con try…finally:




var srv = new Mongo();
try
{
srv.Connect();
// Operaciones con MongoDb
}
finally
{
srv.Disconnect();
}





El membership provider que he creado no implementa todas las funciones, pero sí un grupo suficientemente ámplio para que sea usable: Es capaz de validar usuarios, añadir usuarios y borrar usuarios. P.ej. esta es una captura de pantalla de la aplicación de configuración de ASP.NET usando este proveedor:



image



Nada más… os dejo el enlace al código con un zip que incluye una solución de visual studio con el proveedor y una aplicación asp.net que lo utiliza (una página con un control login). Si os interesa… echadle una ojeada! ;-)



Enlace del fichero .zip (en mi skydrive).



Saludos!



PD: Como no podria ser de otra forma… esto es un crosspost de mi blog en geeks.ms! Pásate por allí mejor… ;-)

martes, 2 de febrero de 2010

Facebook Connect (iv): Que todo el mundo sepa lo que has hecho!

Se comenta que las redes sociales dan fama, mujeres y dinero aunque no necesariamente en este orden…

En los tres primeros posts sobre facebook connect vimos como permitir al usuario que hiciera login con su cuenta de facebook, como implementar el logout y como crear zonas “privadas” de nuestra web sólo para usuarios de facebook.

Hoy vamos a ir un paso más allá: vamos a ver como podemos publicar mensajes en el muro del usuario de facebook autenticado. De esta manera sus amigos verán los logros que nuestro usuario consigue en nuestra web y más importante aún: verán nuestra web! Si el mensaje es sugerente podremos conseguir un buen puñado de visitas a nuestra web! Todos ganamos! El usuario consigue fama y mujeres, y nosotros… dinero. Lo que decía al principio :)

1. Montando toda la infrastructura…

Aprovechando que ha salido ya ASP.NET MVC 2 RC, este es un buen momento para empezar a usarlo, no? ;-)

Descargaos ASP.NET MVC 2 RC e instaláoslo. Una novedad es que ahora (a diferencia de MVC 1) podemos crear una aplicación MVC vacía… parece una chorrada, pero cuando se hacen demos se agradece no ir por ahí borrando el controlador Account y todas sus vistas asociadas! ;-)

Vamos a hacer un proyecto super simple: un botón para que usuario pueda entrar sus credenciales de facebook, y una vez las haya entrado, le mostraremos una página donde podrá entrar un texto que será publicado en el perfil del usuario previa aceptación suya.

Dadle a “New project” –> “ASP.NET MVC 2 Empty Web Application”. Esto os creará un proyecto vacío, pero con la estructura de ASP.NET MVC.

Recordad que para que connect os funcione debéis tener creada vuestra aplicación facebook y el fichero xd_receiver.htm debe estar en vuestra aplicación (tal y como se cuenta en el primer post de esta serie). Ah, y no os olvideis de añadir una referencia al Facebook Developer Toolkit (facebook.dll).

2. Definiendo la aplicación

Nuestra aplicación va a ser simple: un botón de facebook connect para que el usuario se auntentique. Una vez autenticado vamos a pedirle que entre un texto que será publicado en su muro (recordad: previa aceptación).

Para ello vamos a tener un controlador (vamos a ser originales y vamos a llamarlo Home) con tres vistas: la vista inicial (Index) desde donde el usuario podrá autenticarse con su cuenta de facebook, otra vista (Welcome) donde redireccionaremos a los usuarios autenticados para que entren un texto que se publicará en su muro y finalmente la vista Publish que será la que publicará el elemento en el muro del usuario.

3. El controlador Home

Vamos a empezar por el controlador. Vamos a tener dos acciones: Index que se limitará a mostrar la vista inicial y Welcome que mostrará la vista con el campo de texto o bien lo publicará en facebook, dependiendo de si se entra via GET o via POST.

La acción Index es muy simple:

public ActionResult Index()
{
ConnectSession cs = this.GetConnectSession();
if (cs.IsConnected())
{
return RedirectToAction("Welcome");
}
return View();
}





El método GetConnectSession es un método extensor de Controller que me devuelve una ConnectSession (clase que pertence al Facebook Developer Toolkit), que está definido así:




public static class ControllerExtensions
{
public static ConnectSession GetConnectSession(this Controller self)
{
string appKey = ConfigurationManager.AppSettings["ApiKey"];
string secretKey = ConfigurationManager.AppSettings["Secret"];
return new ConnectSession(appKey, secretKey);
}
}





Efectivamente en mi web.config tengo mi clave API y mi clave secreta como elementos dentro de <appSettings />



La acción de Welcome es muy parecida a la acción Index, salvo por la diferencia de que en Index mirábamos si el usuario ya estaba autenticado para redirigirlo a Welcome y aquí haremos al revés: si no está autenticado lo mandaremos para Index:




public ActionResult Welcome()
{
ConnectSession cs = this.GetConnectSession();
if (!cs.IsConnected())
{
return RedirectToAction("Index");
}

return View();
}





4. Las vistas…



Necesitamos (de momento) dos vistas: Home/Index.aspx y Home/Welcome.aspx. La primera debe contener simplemente un botón de facebook connect, renderizado usando XFBML. No pongo el código aquí, ya que es idéntico al del primer post de esta serie y luego tenéis el adjunto con todo el código…



Me interesa más que veais la vista Welcome. Esta vista debe renderizar un cuadro de texto para que el usuario pueda entrar el texto que luego debemos publicar en el muro. El código de la vista Welcome es:




<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<WallPublish.Models.WallMessageModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Welcome
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>Welcome</h2>
<table>
<tr>
<% using (Html.BeginForm()) { %>
<td>
<%= Html.LabelFor(x=>x.Text) %>
</td>
<td>
<%= Html.EditorFor(x=>x.Text) %>
<%= Html.ValidationMessageFor(x=>x.Text) %>
</td>
<% } %>
</tr>
</table>

</asp:Content>





Lo más interesante aquí es el uso de templated helpers para renderizar un editor para la propiedad Text del modelo WallMessageModel. El código Html.EditorFor(x=>x.Text) es quien renderiza un editor para la propiedad Text del modelo. En mi caso esta propiedad es de tipo string, por lo que se va a renderizar un textbox. También es interesante observar el uso de Html.ValidationMessageFor que va a permitir poner mensajes de error en caso de que alguno de los datos entrados sea incorrecto.



Ah, el modelo WallMessageModel es super simple: solo una propiedad Text de tipo string. La peculiaridad es que vamos a usar Data Annotations para evitar que el texto que se entre sea una cadena vacía:




public class WallMessageModel
{
[Required(ErrorMessage="Text es obligatorio")]
public string Text { get; set; }
}





El uso de [Required] en la propiedad Text, convierte esta en obligatoria.



Ok, ya tenemos la vista que renderiza un formulario (Html.BeginForm). Por defecto el formulario se envía via POST a la misma URL de la vista (Home/Welcome en nuestro caso), por lo tanto vamos a necesitar una nueva acción Welcome en el controlador Home pero que trate peticiones POST:




[HttpPost()]
public ActionResult Welcome(WallMessageModel data)
{
if (ModelState.IsValid)
{
return View("Publish", data);
}
else
{
return View(data);
}
}





Fijaos en tres cosas:




  1. El uso de HttpPost hace que esta acción trate solamente peticiones via POST.


  2. El parámetro de la acción es de tipo WallMessageModel. ASP.NET MVC es capaz de crear un objeto de tipo WallMessageModel siempre y cuando la petición POST contenga todos los datos para crearlo. En nuestro caso dado que hemos metido en el formulario un editor para la única propiedad de la clase, la petición POST va a contener los datos suficientes y ASP.NET MVC nos va a poder crear un objeto WallMessageModel con los datos entrados por el usuario.


  3. El uso de ModelState.IsValid para comprobar si el modelo recibido es correcto: esto aplica las reglas de Data Annotations y devuelve true si el modelo las cumple o no. Si el usuario ha entrado un texto vació Model.IsValid será false. Si este es el caso “simplemente” mandamos el modelo “de vuelta” a la misma vista. Esto renderizará de nuevo la vista, con los valores rellenados por el usuario, y el mensaje de error (gracias al uso de Html.ValidationMessageFor). Por otro lado si todo ha ido bien, manda el usuario a la vista “Publish” y le pasa el modelo.



5. La vista de publicación



Sólo nos queda crear la vista que publica el texto en facebook. Para ello vamos a usar el método streamPublish del API de facebook. Este método publica una variable de tipo attachment que tiene varios campos (p.ej. el texto, una URL, el tipo de attachment por si queremos publicar imágenes, …). La descripción completa de todos los campos está en la definición de attachment en la wiki de facebook developer.



A modo de ejemplo vamos a usar los campos:




  • name: Título del post


  • href: Una URL que apunta a “quien ha realizado el post”.


  • caption: El subtítulo del post


  • description: El texto largo del post.



En mi caso dejo los 3 primeros fijos y en el cuarto pongo el valor que ha entrado el usuario:




<script type="text/javascript">
FB.init("TU_CLAVE_API", "/xd_receiver.htm");
FB.ensureInit(function() {
var attachment = { 'name': 'TestGeeks', 'href': 'http://geeks.ms', 'caption': '{*actor*} ha hecho algo',
'description': '<%= Model.Text %>'
};

FB.Connect.streamPublish('', attachment);
});
</script>
<h2>Mensaje publicado</h2>





Fijaos en el uso de {*actor*}: esto se sustituye por el nombre del usuario al que publicamos el mensaje…



Si ejecutais el código vereis como al entrar un texto en el textbox y pulsar enter, facebook va a pedir confirmación del texto a publicar en el muro. Si la aceptáis… el mensaje será publicado en el muro!



Notáis el tintineo de los euros que van cayendo??? :)



Saludos!!!



Os dejo el zip con todo el código (en mi skydrive). Para cualquier duda que tengáis… ya sabéis: contactad conmigo!!!



PD:Para variar esto es un crosspost desde mi blog en geeks.ms… Pásate por allí mejor!! ;-)

[CatDotNet] Material de mi charla sobre ASP.NET MVC

foto1

Hola! Este viernes, tal y como anunció José Miguel, hemos celebrado una pequeña sesión en CatDotNet donde he tenido el placer de hablar un poco sobre ASP.NET MVC…

En la charla vimos los aspectos básicos del nuevo framework, y algunas de las novedades de la próxima versión 2 (como Templated Helpers o validación del modelo). Durante la charla construimos paso a paso una mini-aplicación con MVC y fuimos comentando distintas opciones y el por qué de cada cosa.

Fue muy divertido, y me lo pasé realmente genial!

Os adjunto el material de la charla: las 11 tristes diapositivas que usé (ya digo, fue una sesión básicamente práctica) y la aplicación de demo que hicimos.

A todos los que vinisteis pese a la hora (un viernes a las 19… buf! que pereza!) muchas gracias, espero que disfrutarais como yo.

foto2

Por último sólo recordaros que en CatDotNet seguimos al pie del cañón, organizando charlas y en general lo que podemos: si eres de por la zona de la “Catalunya central” y quieres que te informemos sobre las acciones que realizamos, o bien quieres contarnos tus experiencias con la tecnologia .NET… adelante! Ponte en contacto conmigo o con José Miguel.

Os dejo el enlace al zip con la demo (en mi skydrive).

Saludos!

PD: Edito para poner algunas fotillos… :)

PD: Esto es un crosspost de mi blog en geeks.ms… porque no te pasas por allí mejor?? ;)

jueves, 21 de enero de 2010

[WebCast] Aprovecha el poder de facebook en tu web.

Hola a todos! Este post es sólo para informaros que el próximo 4 de febrero a las 19:00, voy a realizar un webcast para la gente del Club .NET UOC. Bajo el título Aprovecha el poder de facebook en tu web, voy a contar algunas cosillas sobre como integrar facebook en tu aplicación ASP.NET: usar connect para implementar un SSO (single sign-on), poner mensajes en el muro, obtener información del perfil del usuario…

La idea es ver lo fácil que resulta la integración y las ventajas que podemos obtener al integrarnos con una red social en general, y en una de las más usadas en particular…

Os dejo el enlace para apuntaros. Cualquier cosa que queráis comentar… ya sabéis donde encontrarme!!! ;-)

Saludos!

PD: Evidentemente, muchas gracias a la gente del Club .NET UOC y a Jesús Bosch en particular, por invitarme a realizar el webcast… es un autentico placer!

PD: Otro crosspost desde mi blog en geeks.ms (pásate por allí mejor… :p)

miércoles, 20 de enero de 2010

RA000 (o MSB3217): Cannot Register assembly Foo.dll (Method does not have an implementation).

Hola! Un post cortito, sobre un error que me he encotrado… Al compilar un proyecto, marcado para interoperabilidad COM VS.NET se me ha quejado con el siguiente error:

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3019,9): error MSB3217: Cannot register assembly "C:\Teamserver\Phoenix\Refactoring\Core\DevelopmentCore-WI5825-SIO4\bin\Debug\PhoenixContainer.dll". Method 'GetDefaultIWorkspace' in type 'CaixaPenedes.Phoenix.Core.CompositeUI.ShellUserControl' from assembly 'Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=77c76132715b70fa' does not have an implementation.

Yendo al directorio bin/Debug y ejecutar regasm PhoenixContainer.dll daba el mismo error.

La situación era la siguiente:

  1. Tengo dos soluciones distintas, una de las cuales compila (entre otros) el ensamblado Core.dll, donde está la clase ShellUserControl que supuestamente tiene el método no implementado. Por supuesto el método está implementado. La otra solución compila PhoenixContainer.dll, el ensamblado que me da el error.
  2. PhoenixContainer.dll tiene una referencia (entre otros a Core.dll).
  3. Ambas soluciones compilan contra el mismo directorio de salida.

Buscando por ahí, he visto que más gente tenía el mismo error… en esta página dan con la que parece ser la causa principal: que al ejecutar regasm, los ensamblados que regasm usen no sean los mismos contra los que se ha compilado el proyecto. Generalmente eso puede ser debido a versiones incorrectas en la GAC.

Este no era mi caso: Yo tenia Core.dll y PhoenixContainer.dll en el mismo directorio y no uso la GAC, así que es imposible que me estuviese pillando alguna otra Core.dll.

Finalmente he decidido poner en marcha fuslogvw y que me mostrase todos los "bind failures”, es decir todas las veces que el CLR intenta cargar un ensamblado, y por la razón que sea no lo encuentra. Y touché: Ha aparecido un bind failure: Regasm.exe intentaba cargar Microsoft.Practices.Composite.UI.dll (este ensamblado forma parte de CAB). El ensamblado Core.dll tiene una referencia contra Microsoft.Practices.Composite.UI.dll, pero con copy local a false, puesto que usamos un directorio “compartido” donde hay varios ensamblados externos a nuestro proyecto. Sospecho que la razón por la cual Regasm.exe intenta cargar Microsoft.Practices.Composite.UI.dll (y no otros ensamblados también referenciados por Core.dll) es porque el método GetDefaultIWorkspace es público y devuelve un IWorkspace, tipo definido en este ensamblado. Lo curioso, es que este método nunca es utilizado desde PhoenixContainer.dll (aunque el tipo ShellUserControl sí).

Así en el bin/debug tenía Core.dll pero no Microsoft.Practices.Composite.UI.dll y esa era la causa del error: modificando la referencia con copy local a true, todo ha funcionado correctamente!

Cada vez tengo más claro que el copy local a false, sólo trae que compilaciones, a excepción que los archivos referenciados estén en la GAC…

Saludos!

PD: Esto es (como siempre) un crosspost desde mi blog en geeks.ms!

jueves, 14 de enero de 2010

Objetos que notifican sus cambios de propiedades (2/3): Publish and subscribe

Nota: Este post es el segundo post de la serie Objetos que notifican sus cambios de propiedades.

En el post anterior vimos como configurar Unity para que no tener que añadir código adicional para implementar la interfaz INotifyPropertyChanged. En este post quiero hablaros de un patrón que se utiliza mucho cuando hablamos de aplicaciones complejas: el patrón del publicador – suscriptor. En este patrón tenemos básicamente dos conceptos:

  1. El publicador: Cuando un objeto quiere notificar algo al respecto de su estado, se limita a publicar un mensaje con la información deseada.
  2. El suscriptor: Los subscriptores reciben todos aquellos mensajes a los que están suscritos, con independencia de quien los haya publicado.

Este patrón se diferencia del modelo de eventos estándard de .NET, en que para realizar una suscripción a un tipo de mensaje no es necesario tener referencia alguna a quien pueda publicar este mensaje. En el sistema de eventos no és así: si quiero recibir información sobre el click de un botón, debo tener una referencia a este botón, para poder registrar la función gestora del evento:

button1.Click += new EventHandler(button1_Click);





Este modelo de eventos directos tiene sus limitaciones y da en Winforms bastantes quebraderos de cabeza (especialmente cuando tenemos un formulario con un usercontrol formado por varios usercontrols que a su vez están formados por más usercontrols y queremos propagar un evento del usercontrol  más interno al formulario). Es cierto que WPF introduce dos mejoras interesantes como los routed events (que ayudan precisamente a solventar este problema de usercontrols anidados) y los commands, pero ninguno de ambos mecanismos ofrece la misma flexibilidad que el modelo de publicación – suscripción.



Créeme: si desarrollas una aplicación compleja, ya sea en winforms o en WPF, te beneficiará mucho el uso de un modelo de publicación – suscripción (no en vano tanto CAB+SCSF como PRISM incorporan uno).



Vamos a ver como podemos implementarnos uno que, aunque sencillito, sea lo suficientemente funcional…



1. El notificador de mensajes



Lo primero que debemos crear es el notificador de mensajes, es decir el objeto que usamos para publicar un mensaje y el que usamos también para informar a que tipo de mensajes queremos suscribirnos.



El notificador de mensajes va a tener esta interfaz:




public interface ICommandNotifier
{
/// <summary>
/// Devuelve la lista de los commands actuales. Los commands se
/// añaden automáticamente cuando se realiza un publish de cualquier
/// tipo nuevo.
/// </summary>
IEnumerable<Type> Commands { get; }
/// <summary>
/// Añade una suscripción al tipo de command TPayload
/// </summary>
/// <typeparam name="TPayload">Tipo de command al que nos suscribimos</typeparam>
/// <param name="func">Acción a ejecutar cuando se publique el command</param>
/// <param name="filterFunc">Método que se evalúa sobre el payload para determinar
/// si el command se pasa o no al suscriptor.</param>
/// <returns>Token de suscripción</returns>
SubscriptionToken Subscribe<TPayload>(Action<TPayload> func, Func<TPayload, bool> filterFunc);

/// <summary>
/// Publica un command. El tipo de command es el tipo de la clase del payload.
/// </summary>
/// <param name="payload">Payload (datos) del commanad</param>
void Publish(object payload);





Básicamente sólo tiene un método para suscribirse a un determinado tipo de mensajes y otro método para publicarlos. Una implementación más compleja nos permitiría también eliminar suscripciones (es decir cuando ya no me interesa seguir recibiendo notificaciones de determinados commands)… pero eso lo dejamos como ejercicio :)



La implementación tampoco es excesivamente compleja (no pongo el código aquí, ya que lo tenéis en el zip que adjunto al final del post). Básicamente lo que hace es:




  1. Mantiene una lista de todos los tipos de mensajes que se hayan lanzado. Lo que determina si un mensaje es de un tipo u otro es su clase (en la implementación una lista de objetos CommandInfo).


  2. Por cada mensaje de esa lista mantiene una lista con todos los suscriptores (en la implementación objetos de la clase AllTimeSubscriber).


  3. Por cada suscriptor de cada mensaje mantiene básicamente dos delegates:

    1. El delegate que sirve para decidir si se envía este mensaje a este suscriptor (parámetro filterFunc del método Subscribe)


    2. El delegate que debe invocarse en el suscriptor (parámetro func del método Subscribe).





Sólo un apunte: el notificador de mensajes vamos a registrarlo en Unity como un singleton, eso significa que existirá sólo uno y que estará vivo durante toda la ejecución del programa. Por lo tanto, si guardamos directamente los delegates en el notificador de mensajes, impedirá al garbage collector actuar sobre los suscriptores (recordad que un delegate mantiene una referencia a un objeto en concreto y a un método). Para solucionar esto me he creado una clase, que he llamado WeakDelegate, que tiene la misma información que un delegate, pero usa una WeakReference para apuntar al objeto (el suscriptor) y de esta manera permitir actuar al garbage collector. Recordad: siempre que guardeis referencias en un singleton considerad el uso de WeakReference!



2. Cambiar la implementación de nuestro handler



Una vez tenemos un notificador de mensajes, sólo debemos cambiar la implementación de nuestro ICallHandler (clase AutoPropertyChangedHandler) de Unity, para usar dicho notificador. Para ello en el método Invoke en lugar de llamar al método RaiseEvent (para lanzar el evento PropertyChanged) como hacíamos en el post anterior, vamos a usar el notificador para publicar un mensaje de tipo PropertyChangedCommand:




// Si el setter no produce excepción, publicamos un command de tipo PropertyChangedCommand
if (raiseEvt && msg.Exception == null)
{
cmdNotifier.Publish(new PropertyChangedCommand(propName, input.Target));
}





La clase PropertyChangedCommand es una clase que nos hemos creado nosotros que no hace nada más que guardar el nombre de la propiedad que ha cambiado y el objeto sobre el cual ha cambiado la propiedad.



Como recibe la clase AutoPropertyChangedHandler el notificador de mensajes? Pues se le pasa en el constructor:




public AutoPropertyChangedHandler(ICommandNotifier cmdNotifier)
{
this.cmdNotifier = cmdNotifier;
}





Ahora sólo debemos modificar la clase AutoPropertyChangedAttribute para que cuando cree el objeto AutoPropertyChangedHandler  le pase el notificador de mensajes. La forma más fácil es aprovechar que en AutoPropertyChangedAttribute tenemos acceso a Unity, para devolver el objeto AutoPropertyChangedHandler usando Resolve y que de esa manera Unity inyecte el notificador de mensajes:




public override ICallHandler CreateHandler(IUnityContainer container)
{
return container.Resolve<AutoPropertyChangedHandler>();
}





3. El suscriptor



Finalmente nos queda crear el suscriptor. Los suscriptores son clases normales que usan el notificador de mensajes para suscribirse a tipos de mensajes. P.ej. el siguiente suscriptor se suscribe a los mensajes cuyo tipo sea PropertyChangedCommand:




public Suscriptor(ICommandNotifier cmdNotif)
{
cmdNotif.Subscribe<PropertyChangedCommand>(this.DoPropertyChangedCommand, this.CanDoPropertyChangedCommand);
}

private void DoPropertyChangedCommand(PropertyChangedCommand payload)
{
Console.WriteLine("Propiedad {0} modificada", payload.PropertyName);
}

private bool CanDoPropertyChangedCommand(PropertyChangedCommand payload)
{
bool retVal = !payload.PropertyName.Equals("Name");
Console.WriteLine("CanDoPropertyChanged con prop {0} devuelve {1}", payload.PropertyName, retVal);
return retVal;
}





Fíajos en la función CanDoPropertyChangedCommand: esta función se evalúa cada vez que alguien publica un command y sólo en el caso que devuelva true se ejecutará la función DoPropertyChangedCommand que es la que “procesa” el mensaje. En este caso, este suscriptor está interesado en recibir todos los cambios de calquier propiedad excepto “Name”.



Finalmente sólo nos queda crear un suscriptor y probar el código. En el método Main() tengo:




container.RegisterType<ICommandNotifier, CommandNotifier>(new ContainerControlledLifetimeManager());
A2 a2 = container.Resolve<A2>();
Suscriptor subs = container.Resolve<Suscriptor>();
a2.Name = "edu";
a2.Edad = 10;
// Registramos el notificador como singleton
container.RegisterType<ICommandNotifier, CommandNotifier>(new ContainerControlledLifetimeManager());
// Creamos un A2...
A2 a2 = container.Resolve<A2>();
// ... y un suscriptor
Suscriptor subs = container.Resolve<Suscriptor>();
a2.Name = "edu";
a2.Edad = 10;





Y listos! Sí lo ejecutais veréis que la salida es:



CanDoPropertyChanged con prop Name devuelve False

CanDoPropertyChanged con prop Edad devuelve True


Propiedad Edad modificada



Ya tenemos implementado nuestro propio publicador-suscriptor!



Os dejo un zip con todo el código (en skydrive).



Un saludo!



PD: Esto es un crosspost desde mi blog en geeks.ms! Pásate por allí! ;-)

miércoles, 13 de enero de 2010

Objetos que notifican sus cambios de propiedades (1/3): La intercepción

Nota: Este post es el primer post de la serie Objetos que notifican sus cambios de propiedades.

En este post vamos a ver como configurar la intercepción de Unity, para poder inyectar nuestro código cada vez que se modifiquen las propiedades de un objeto.

Los que desarrolléis en WPF sabréis que existe una interfaz llamada INotifyPropertyChanged, que se puede implementar para notificar a la interfaz de usuario de que las propiedades de un objeto (generalmente ligado a la interfaz) han modificado, y que por lo tanto la interfaz debe actualizar sus datos.

Esta interfaz define un solo evento, llamado PropertyChanged que debe lanzarse para informar del cambio de propiedad. Es responsabilidad de cada clase lanzar el evento cuando sea oportuno:

public class A : INotifyPropertyChanged
{
private string _name;

// Evento definido por la interfaz
public event PropertyChangedEventHandler PropertyChanged;

// Lanza el evento "PropertyChanged"
private void NotifyPropertyChanged(string info)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
// Propiedad que informa de sus cambios
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
}





Este código es pesado de realizar en clases con muchas propiedades y es fácil cometer errores… además no podemos utilizar las auto-propiedades. Vamos a ver como con el sistema de intercepción de Unity podemos hacer que este evento se lance de forma automática.



1. Configurando el sistema de intercepción de Unity



Para usar el sistema de intercepción de Unity, debéis añadir los assemblies Microsoft.Practices.ObjectBuilder2, Microsoft.Practices.Unity y Microsoft.Practices.Unity.Interception a vuestro proyecto (los tres assemblies forman parte de Unity).



El primer paso es crear una clase que implemente la interfaz ICallHandler, esta clase es la encargada de proporcionarnos un punto dónde inyectar el código:




class AutoPropertyChangedHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
// Aquí podremos inyectar nuestro código
IMethodReturn msg = getNext()(input, getNext);
return msg;
}
public int Order { get; set; }
}





El siguiente paso es crear un atributo que permita indicar a Unity que ICallHandler debe usar cuando se opere con objetos de la clase. Esta clase debe derivar de HandlerAttribute y debe redefinir el método CreateHandler para devolver una instancia del ICallHandler a utilizar:




class AutoPropertyChangedAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new AutoPropertyChangedHandler();
}
}





El código es trivial, eh?? Simplemente devuelve un AutoPropertyChangeHandler, de esa manera Unity usará este AutoPropertyChangeHandler para todas las clases que estén decoradas con el atributo AutoPropertyChangedAttribute. De esta manera es como le indicamos a Unity qué ICallHandler debe usar por cada tipo de clase.



Finalmente queda configurar el propio contenedor. Para ello debemos debemos añadir la extensión de intercepción a Unity y indicarle que interceptor queremos utilizar para cada clase:




IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();
container.Configure<Interception>().SetInterceptorFor<A2>(new VirtualMethodInterceptor());





Le he indicado a Unity que para la clase A2 utilice el interceptor VirtualMethodInterceptor. Este interceptor puede interceptar todas las llamadas a métodos virtuales.



Finalmente ya podemos definir la clase A2:




[AutoPropertyChanged()]
public class A2 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual string Name { get; set; }
}





Fijaos en las diferencias entre A2 y la clase A antigua:




  1. A2 está decorada con el atributo AutoPropertyChangedAttribute que hemos definido antes.


  2. La propiedad Name es virtual


  3. No tenemos ningún código adicional para lanzar el evento PropertyChanged.



Con eso el mecanismo de intercepción está listo. Si obteneis una instancia de A2 usando el método Resolve del contenedor y miráis con el debugger de que tipo real es el objeto, veréis que no es de tipo A2, sinó de un tipo raro: el proxy que crea Unity para poder interceptar los métodos virtuales (comparad el wach para a2 y a2newed:



image



2. Implementando el código en nuestro Handler



Vamos a modificar el método Invoke de AutoNotifyPropertyHandler para que lance el evento cada vez que se llame a un set de una propiedad… La clase completa queda tal y como sigue: Básicamente en el método Invoke, miramos si el nombre del método que se ha llamado empieza por “set_”, y si es el caso asumimos que es el Setter de una propiedad. Recogemos el valor actual de la propiedad usando reflection y si no son el mismo, lanzamos el evento PropertyChanged usando reflection. Y listos! :)




class AutoPropertyChangedHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
bool raiseEvt = false;
string propName = null;
INotifyPropertyChanged inpc = input.Target as INotifyPropertyChanged;
if (inpc != null)
{
// Si el nombre del método empieza por "set_" es un Setter de propiedad
if (input.MethodBase.Name.StartsWith("set_"))
{
propName = input.MethodBase.Name.Substring(4);
MethodInfo getter = input.Target.GetType().GetProperty(propName).GetGetMethod();
object oldValue = getter.Invoke(input.Target, null);
object newValue = input.Arguments[0];
// Si los valores de newValue y oldValue son distintos
// debemos lanzar el evento (la comparación la hacemos por
// Equals si es posible).
raiseEvt = newValue == null ?
newValue != oldValue :
!newValue.Equals(oldValue);
}
}
IMethodReturn msg = getNext()(input, getNext);
// Si el setter no produce excepción, lanzamos el evento
if (raiseEvt && msg.Exception == null)
{
RaiseEvent(inpc, propName);
}
return msg;
}

public int Order { get; set; }

// Método que lanza el evento PropertyChanged usando reflection
private void RaiseEvent(INotifyPropertyChanged inpc, string propertyName)
{
Type type = inpc.GetType();
FieldInfo evt = null;
// Buscamos el evento (no estará en el propio Type ya que el propio Type
// será un proxy, pero iteraremos por los tipos base hasta encontrarlo)
do
{
evt = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
type = type.BaseType;
} while (evt == null && type.BaseType != null);
// Invocamos el evento
if (evt != null)
{
MulticastDelegate mcevt = evt.GetValue(inpc) as MulticastDelegate;
if (mcevt != null)
{
mcevt.DynamicInvoke(inpc, new PropertyChangedEventArgs(propertyName));
}
}
}
}





Ahora podemos comprobar como se lanza el evento automáticamente al modificar una propiedad de A2.



Un saludo!!!!



PD: Dije en el post introductorio que no pondría código, pero finalmente os incluyo el zip con el código de este post (en SkyDrive).



PD2: Echad un post a INotifyPropertyChanged with Unity Interception AOP del blog de Dmitry Shechtman que me ha servido de inspiración para el ejemplo (yo tenía pensado uno mucho más tonto en este primer post).



PD3: Esto es un crosspost desde mi blog en geeks.ms!