[Windows 8] IoC & MVVM

Hola a todos!

Uno de los puntos clave del patrón MVVM y donde he visto más formas de aplicarlo es en lo referente a la unión entre Views y ViewModels. Hay gente que las asigna en code behind, otros lo hacen referenciando directamente a la ViewModel en XAML o Cargan las ViewModels y hacen que las Views sean DataTemplates que se resuelvan mediante converters… realmente hay mil formas de hacerlo, algunas más acertadas que otras, pero todas válidas.

Tradicionalmente a mi me gusta usar una clase Locator, que se encargue de resolver la instancia del ViewModel y sus dependencias y exponerla mediante una propiedad pública para poder enlazarnos a ella mediante un Binding en XAML. Esto además contando con el apoyo de Unity para resolver las dependencias del ViewModel, se convierte en un sistema muy ágil y rápido, ofreciéndonos un punto único dentro de nuestra aplicación donde resolver todo lo necesario para que el ViewModel funcione.

Lamentablemente hasta hace relativamente poco tiempo, en WinRT no contábamos con ningún mecanismo de IoC o DI que nos ayudase con esta tarea, teniendo que relegar el trabajo en implementar nuestra clase Locator de una forma más “manual”. Supongamos que tenemos un ViewModel que depende de un servicio que contiene los métodos de acceso al Api del GPS, este sería:

  1: public class VMMainPage : VMBase
  2: {
  3:     private IGpsService gpsService = null;
  4: 
  5:     public VMMainPage(IGpsService gpsSrv)
  6:     {
  7:         gpsService = gpsSrv;
  8:     }
  9: 
 10:     public double ActualAltitude
 11:     {
 12:         get
 13:         {
 14:             return gpsService.GetActualAltitude();
 15:         }
 16:     }
 17: }

Y el servicio de GPS sería este:

  1: public interface IGpsService
  2: {
  3:     double GetActualAltitude();
  4: }
  5: 
  6: public class GpsService : IGpsService
  7: {
  8:     public double GetActualAltitude()
  9:     {
 10:         return 100.0;
 11:     }
 12: }

El usar la interface desde nuestro ViewModel nos va a permitir aislarnos del código del GPS (aunque en este caso es un simple “fake”) cuando realicemos pruebas unitarias o si decidimos compartir la ViewModel con una aplicación, por ejemplo, Windows Phone.

Para un ViewModel de este tipo, nuestra clase Locator tendría este aspecto:

  1: public class VMLocatorWithoutIoC
  2: {
  3:     private Lazy<VMMainPage> viewModelMainPage;
  4: 
  5:     public VMLocatorWithoutIoC()
  6:     {
  7:         viewModelMainPage = new Lazy<VMMainPage>(() => { return new VMMainPage(new GpsService()); });
  8:     }
  9: 
 10:     public VMMainPage ViewModelMainPage
 11:     {
 12:         get
 13:         {
 14:             return viewModelMainPage.Value;
 15:         }
 16:     }
 17: }

Con este código nuestro ViewModel se comportará como un Singleton, pues el tipo Lazy ejecutará el constructor la primera vez que accedamos a la propiedad ViewModelMainPage y a continuación siempre devolverá esa instancia.

Pero ahora tenemos disponible para nuestras aplicaciones WinRT MetroIoC, un contenedor de IoC, portado a WinRT a partir de MicroIoC y con una sintaxis muy cómoda. Lo más interesante de MetroIoC es que al ser un port de MicroIoC, el modo de uso de ambos es muy parecido y hasta que Microsoft se decida a portar oficialmente Unity, es la forma más comoda de incluir IoC en nuestra aplicación metro.

Para usar MetroIoC solo necesitamos acceder al Administrador de paquetes de NuGet e instalarlo desde allí:

image

Una vez que hayamos instalado MetroIoC, integrarlo en nuestro Locator será realmente sencillo, en primer lugar creamos nuestro contenedor:

  1: private MetroContainer container = new MetroContainer();

Y a continuación le añadimos nuestra ViewModel y sus dependencias:

  1: public VMLocatorWithMetroIoC()
  2: {
  3:     container.Register<VMMainPage>()
  4:         .RegisterInstance<IGpsService>(new GpsService());
  5: }

De esta forma, en nuestra propiedad pública solo tenemos que pedir al contenedor que resuelva el tipo que deseamos devolver:

  1: public VMMainPage ViewModelMainPage
  2: {
  3:     get
  4:     {
  5:         return container.Resolve<VMMainPage>();
  6:     }
  7: }

Aquí podemos configurar más cosas, por ejemplo podemos indicar al contenedor si al resolver el tipo VMMainPage debe conservar una instancia (SingletonLifecycle) o crear una instancia cada vez que la solicitemos (TransientLifecycle) Lo que manualmente nos abría ocupado algunas líneas extra de código aquí podemos indicarlo simplemente usando un parámetro del método Register de nuestro contenedor.

El uso es igual de sencillo que en el primer caso, añadimos la clase VMLocatorWithMetroIoC como un recurso en nuestro archivo app.xaml:

  1: <Application.Resources>
  2:     <ResourceDictionary>
  3:         <ResourceDictionary.MergedDictionaries>
  4: 
  5:             <!-- 

  6:                 Styles that define common aspects of the platform look and feel
  7:                 Required by Visual Studio project and item templates
  8:                 -->
  9:             <ResourceDictionary Source="Common/StandardStyles.xaml"/>
 10:         </ResourceDictionary.MergedDictionaries>
 11:         <Locator:VMLocatorWithoutIoC x:Key="LocatorWithoutIoC"></Locator:VMLocatorWithoutIoC>
 12:         <Locator:VMLocatorWithMetroIoC x:Key="LocatorWithIoC"></Locator:VMLocatorWithMetroIoC>
 13:     </ResourceDictionary>
 14: </Application.Resources>

Y en nuestra página añadirlo al DataContext:

  1: <Page
  2:     x:Class="Win8IoC.MainPage"
  3:     IsTabStop="false"
  4:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  5:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  6:     xmlns:local="using:Win8IoC"
  7:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  8:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  9:     mc:Ignorable="d"
 10:     DataContext="{Binding ViewModelMainPage, Source={StaticResource LocatorWithIoC}}">
 11: 
 12:     <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
 13:         <TextBlock Text="{Binding ActualAltitude}"
 14:                    FontSize="36"
 15:                    VerticalAlignment="Center"
 16:                    HorizontalAlignment="Center">
 17:         </TextBlock>
 18:     </Grid>
 19: </Page>

De esta forma resolvemos la ViewModel mediante IoC, desde un único punto en toda nuestra aplicación y conseguimos que modificar sus dependencias y como se resuelven, sea realmente sencillo. Como siempre aquí os dejo el código del ejemplo para que juguéis con el. A partir de ahora le vamos a dar mucho uso en el blog, vamos a empezar a ver como acceder a los sensores del dispositivo desde nuestra aplicación y para ello haremos un uso intensivo de las dependencias en nuestros ViewModels.

Un saludo y Happy Coding!

3 comentarios sobre “[Windows 8] IoC & MVVM”

  1. Hola Josue,

    No tengo mucha idea de esto, así que mi pregunta igual es una tontería.

    Si tenemos varias vistas, ¿hay que añadir más propiedades al VMLocator o se puede hacer dinámicamente de alguna forma?

    Gracias.

    Juanma.

  2. Por cada ViewModel añadiríamos una propiedad para de esta forma poder realizar el enlace de la propiedad DataContext a la ViewModel que nos interese. Para mi esta forma es la más clara y limpia, podríamos hacerlo más dinámico exponiendo un diccionario que devolviese la viewmodel a partir de una key… pero me parece que introduce más confusión en el código y no es necesario.

  3. Gracias por la aclaración.

    Yo tampoco tengo muy claro que usar un diccionario sea mejor, pero no me acaba de gustar tener que tocar la clase cada vez que añades una nueva vista.

    No sé, tal vez aplicando una convención…

    En cualquier caso, muy buen post.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *