Es verdad que tenía un poco abandonado el blog, y puede parecer que Silverlight también. Lo primero es cierto, pero lo segundo todo lo contrario, ya que en este tiempo he estado asignado a varios proyectos Silverlight. Con un poco de suerte cuaja este nuevo cacharro de Microsoft y las empresas se toman en serio esta nueva tecnología.
Y ahora, cuando más de uno está destripando PRISM, como por ejemplo el compañero Braulio Diez, que por cierto ha sido nombrado recientemente MVP en Silverlight, llega el menda con un post de MVVM. Desde aquí, y antes de continuar, dar mi más sincera enhorabuena a Braulio por este reconocimiento – no sé realmente cuanta gente opta a estos premios, pero si estoy seguro que nadie se lo merecía más que él. Todo un lujo trabajar a su lado. Congratulations (es que se empeña en que escriba en ingles
).
MCV, MVP, y ahora MVVM, Model View ViewModel. Aunque ya se ha dicho y escrito mucho sobre este modelo, voy a intentar dar una visión más práctica, después de haber aplicado el modelo en diversas aplicaciones.
Un poco de teoría e historia
-
MVVM es un patrón de diseño especialmente diseñado para las aplicaciones definidas con WPF.
-
Un patrón de diseño nos tiene que ayudar a definir la arquitectura a la hora de diseñar una aplicación, identificando problemas y aportando soluciones.
En el caso que nos atañe, MVVM, es un patrón definido a partir del patrón MVC (Model View Controller) y MVP (Model View Presenter), de los que claramente identificamos ciertas partes en común.
-
Model: Identifica a la lógica de negocio. Acceso a datos, y operaciones sobre los mismos
-
View: Define a la Interfaz que ve el usuario.
-
Controller / Presenter: Gestiona las operaciones que efectúa el usuario sobre la Vista, accediendo al Model si la operación lo requiere.
Teniendo en cuenta estas definiciones, tenemos que destacar unas pequeñas diferencias con respecto a MVVM. Un detalle que no podemos dejar pasar por alto es el hecho de que una aplicación Silverlight está definida entre el Cliente y el Servidor pero podemos situarla más cerca de una aplicación winform que de una aplicación web. Teniendo esto en mente definamos cada una de las partes que componen el patrón.
-
View: Al igual que en MVC y MVP, la View siguen siendo la Interfaz de Usuario, está definida en XAML (aunque también es posible definirla en el codebehind asociado). La vista mostrara datos y generará eventos como respuesta a las acciones del usuario. Un dato importante, los controles estarán enlazados al modelo mediante Data Binding.
-
ViewModel: Será el encargado de manejar los eventos que genera la View, ya sea recogiendo o mostrando datos. Es la capa intermedia del patrón que comunicara el Modelo con la Vista. Esta capa será la encargada de preparar los datos del modelo para que puedan ser consumidos por la Vista.
-
Model: Esta capa será la encargada de mantener la persistencia de los datos. En Silverlight esto se hace mediante Web Services. El modelo de datos, lo obtendremos al generar la referencia sobre los servicios web, definiendo automáticamente un clase proxy para tal efecto.
La idea fundamental de los modelos en capas es que cada una de las partes tengan mínimo acoplamiento y máxima cohesión, es decir cada una de ellas puedas existir perfectamente pero a su vez la conjunción de todas ellas funcione de una manera óptima.
En un primer momento podríamos decir que MVVM es MVP/C con otro nombre, pero no, hay algo más. Para que todo esto funcione necesitamos descubrir el engranaje que se escode detrás de cada una de las ruedas que forman el reloj.
Binding Automático.
El Data Binding, esa es la gran diferencia. Esta característica que nos ofrece WPF es la encargada de enlazar la interfaz con el modelo o contexto de datos, de tal manera que un cambio en el modelo de datos se replique automáticamente en la interfaz y a su vez un cambio en la interfaz se transfiera de forma automática sobre el modelo. Y todo esto sin hacer prácticamente nada J.
Arquitectura de un proyecto.
Una aplicación Silverlight (solución de Visual Studio) la podemos dividir en dos partes. Una parte en Cliente, la aplicación en sí, y otra en Servidor, el acceso y el modelo de datos. Esta podría ser la arquitectura de una aplicación Silverlight.
Manos a la obra.
Una vez hayamos creado el armazón de nuestro proyecto y tengamos cada uno de las capas definidas, pasemos a ver de qué estará compuesta cada una de ellas. Voy a describir cada una de ellas tal vez a primera vista desordenado, pero es el orden lógico que sigo personalmente, el cual seguramente no sea el mejor, pero como dije al principio, el artículo se basa en la experiencia obtenida en el desarrollo de varias aplicaciones con el patrón Model View ViewModel. Por lo tanto el orden en que voy a explicar las capas será el siguiente:
1. Client.View
2. Server.Entities
3. Server.DataLayer
4. Server.Web
5. Client.Entities
6. Client.Model
7. Client.ViewModel
Client.View
Como su nombre indica, aquí encontraremos las interfaces de usuario. Tendremos la página principal, y pagina auxiliares (si hacemos uso del Navigation Framework, como es en nuestro caso). También tendremos los controles de usuario que definamos.
Un objeto View estará definido por un fichero XAML y un fichero code begind asociado. La idea principal es que la interfaz quede casi totalmente definida en el fichero XAML, estableciendo declarativamente el binding de los datos de nuestro modelo y el de otras propiedades de los controles. Cuando decimos “casi” es por el hecho de que no nos queda más remedio que manejar los eventos que queramos manejar en el fichero code behind (a no ser que hagamos uso por ejemplo de PRISM). En cualquier caso, nos quedará una capa de Vista bastante limpia.
Cada View tendrá asociado como data context un ViewModel. Las propiedades del objeto ViewModel serán las que luego usaremos en el data binding.
Cosas a destacar. El objeto DataContext que establezcamos en una View será el usado por cada uno de los controles que incluya la Vista, siempre y cuando que no establezcamos un DataConext particular para ellos. Es decir un control buscará si tiene asociado DataContext, y si no, buscará en su contendor padre y así sucesivamente.
Es muy importante que tengamos claro cómo vamos a maquetar nuestra aplicación, paginas y controles de usuario, ya que en función de esto más tarde intentaremos preparar los objetos de nuestro modelo para facilitarnos el trabajo.
Server.Entities
No pensemos que vamos a definir los objetos entidades en función de las interfaces. Si se ha entendido eso, me he explicado mal. El modelo de entidades representa los objetos que definen un problema, pero sí podemos modelarlos de manera que luego nos sea mas fácil trabajar con ellos.
Podemos tener una View definida con cuatro controles de usuario. Por lo tanto podemos tener un objeto con cuatro propiedades que a su vez definen objetos, las cuales usaremos como data context particular para cada uno de esos controles de usuario. Esto es solo una forma de trabajar, nada pasaría por tener un objeto que agrupe las propiedades de los 4 subentidades y establecer solamente el data context en el objeto View, dejando que los controles hereden de ella.
Si hacemos uso de entidades complejas (es decir entidades compuestas por sub entidades), para evitar problemas es conveniente que establezcamos los atributos [DataMember] y [DataContract] a nuestras clases y atributos.
Server.DataLayer
Aqui tendremos las clases que definan las operaciones que podemos hacer sobre los las entidades de nuestro modelo. Será la capa encargada del acceso a las base de datos. Estas clases serán accedidas por el servicio web.
Server.Web
Este proyecto, además de tener las páginas web que alojarán la aplicación Silverlight, definirá los web services que expondrá tanto el modelo de datos del Servidor a la aplicación Silverlight en cliente como las operaciones que podremos realizar sobre ellos. El servicio web mediante una instancia de un objeto del DataLayer obtendrá el modelo de datos.
Client.Entities
Es nn esta capa donde vamos a añadir la referencia al servicio web creado. Una vez que añadamos esta referencia se creará una clase proxy con todas los operaciónes que ofrece el servicio web y todas las entidades que exponen esas operaciones, creando en cliente el mismo modelo de datos definido en el Servidor.
Todas las entidades extra que necesitemos en Cliente, las añadiremos aquí. Además podemos ampliar el modelo de entidades definidas en Servidor con nuevas entidades. Podremos añadir nuevas propiedades a las entidades existentes, o incluso nos podemos encontrar en la situación de que las propiedades además de establecer y obtener su valor, tiene que realizar alguna operación, para lo cual no nos queda más remedio que implementar una nueva propiedad para tal efecto. Esto lo haremos añadiendo clases parciales al proyecto de entidades.
-
<p class="MsoNormal" style="text-align: justify; margin: 0cm 0cm 10pt;"><span style="font-size: small; font-family: Calibri;">namespace CV_MVVM.Client.Entities.CV_MVVM_Service
-
{
-
public partial class CV
-
{
-
private bool _dataChanged = false;</span>
-
-
//Reescribimos una propiedad pero añadiendo funcionalidad.
-
//En este caso tenemos un flag para indicar que
-
//los datos han sido modificados
-
public String Name_ToBind
-
{
-
get { return Name; }
-
set
-
{
-
Name = value;
-
//Indicamos que se ha producido un cambio en el valor
-
_dataChanged = true;
-
RaisePropertyChanged("Name_ToBind");
-
}
-
}
-
-
}
-
}
La capa de Entities es el canal de comunicación común entre el Model y el ViewModel.
Client.Model
Esta capa es la encargada de establecer la comunicación entre Silverlight y los servicios Web ya sea solicitando datos como realizando operaciones sobre ellos (añadir, modificar, eliminar). Para ello las clases del modelo contendrán los métodos necesarios para realizar estas operaciones.
Destripando una clase del Modelo
Como bien sabemos el modo de comunicación entre Silverlight y el Servidor es mediante Servicios Web, y la respuesta de estos es asíncrona. Esto quiere decir que tras realizar una llamada a un método del servicio web, la respuesta no es inmediata, continuando el flujo de la aplicación. Las respuestas son encoladas ejecutadas en función al orden de llegada. Para establecer un control sobre estas respuestas y controlar pues las acciones a realizar lo que hacemos es añadir eventos públicos a nuestra clase Model. Posteriormente cuando las repuestas “asíncronas” de los web services son procesadas lazamos el evento que hemos creado en repuesta a la peticicion efectuada. Será obligación del ViewModel subscribirse y manejar estos eventos.
-
public class CVModel
-
{
-
-
public CVModel()
-
{
-
}
-
-
public event EventHandler evCVRecieved;
-
-
public void GetCV(string name)
-
{
-
-
EndpointAddress address = new EndpointAddress("<a href="http://localhost:1046/Services/CVService.svc">http://localhost:1046/Services/CVService.svc</a>");
-
-
-
//Indicar funcion que va a capturar la repuesta del webservice
-
-
//Generar llamada al webservice
-
client.GetCVAsync(name);
-
}
-
-
/// <summary>
-
/// Manejar repuesta del web service
-
/// </summary>
-
void client_GetCVCompleted(object sender, GetCVCompletedEventArgs e)
-
{
-
if (e.Result != null)
-
evCVRecieved(this, e);
-
}
-
-
}
Client.ViewModel
Como ya dijimos anteriormente esta capa es la encargada tanto de manejar los eventos sobre la View como de ofrecer los datos consumidos por esta. Teniendo esto en cuenta, una clase ViewModel tendrá métodos para manejar las peticiones de la View y “Propiedades” que expondrán los datos requeridos por esta. Además tendremos manejadores para los eventos a los que la clase ViewModel se subscriba (eventos lanzados por el Model).
Interfaz IPropertyNotifyChanged.
Es interesante hacer que nuestra clases ViewModel implementen la interfaz IPropertyNotifyChanged, de manera que cambios completos sobre las propiedades, es decir que cambiemos el objeto que una propiedad devuelve, se reproduzcan sobre la vista. Tal vez no queda del todo claro este punto, pongamos un ejemplo.
Si por ejemplo tenemos una propiedad “ListaProyectos”, y en nuestra clase ViewModel no implementa la interfaz IPropertyNotifyChanged siempre que queramos establecer la lista por completo tendremos que limpiar su contenido e ir añadiendo uno a uno los elementos. No podemos cambiar simplemente el objeto lista que devuelve la propiedad, ya que con eso no conseguimos que se lanze el evento de nuevo elemento que se produce al añadir elementos sobre una lista de tipo ObservableCollection.
Si por el contrario implementamos la interfaz IPropertyNotifyChanged, simplemente tendriamos que definir los Get y Set para los propiedades y en la defnición del Set además de establecer el valor lanzaríamos el evento PropertyChanged para que la vista fuera notificada del cambio.
-
public class CVModel
-
{
-
-
public CVModel() {}
-
-
public event EventHandler evCVRecieved;
-
-
public void GetCV(string name)
-
{
-
-
-
-
//Indicar funcion que va a capturar la repuesta del webservice
-
-
//Generar llamada al webservice
-
client.GetCVAsync(name);
-
}
-
-
///
-
/// Manejar repuesta del web service
-
///
-
void client_GetCVCompleted(object sender, GetCVCompletedEventArgs e)
-
{
-
if (e.Result != null)
-
evCVRecieved(this, e);
-
}
-
-
}
Terminando...
Siguiendo este flujo hemos conseguido montar una primera instancia de nuestro proyecto, sobre el cual podremos ir trabajando y completando. Tenemos el modelo definido en lado del servidor servidor, en la parte cliente [Client.Entities], tenemos los metodos de acceso a datos sobre el servidor [Client.Model] y tenemos las propiedades que ofrecen los datos a mostrar en las interfaces [Client.ViewModel y Client.View].
Bueno con esta pequeña guia simplemente intento mostrar un punto de entrada al patrón a aquellas personas que se puedan encontrar un poco perdidas entre el data binding y el patrón Model View ViewModel.



Septiembre 29th, 2009 at 11:08
Muy interesante el artículo. La verdad es que resulta bastante esclarecedor para aquellos que, ahora que estamos desvinculados del desarrollo web, no queremos perder el hilo de las nuevas tecnologías que comienzan a coger fuerza en este campo.
¡Un saludo!