Dependency Injection: MVVM Light + WPF + Unity

Bueno, ya sabéis qué es Dependency Injection y para qué puede sernos útil.

Ahora bien… ¿Como implementamos eso en WPF y MVVM Light?

Faacil faacil. Para este ejemplo vamos a usar Unity que es una de las muchas librerías para Dependency Injection.

Vamos a ello:

Creamos una nueva aplicación usando la plantilla de MVVM Light, la llamaremos MVVMWithDI.

Vamos a cambiar nuestra ventana MainWindow.xaml por algo simple creado rápidamente en Blend:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MVVMWithDI.MainWindow"
        Title="MVVM Con DI"
        Height="304"
        Width="260"
        Background="#FFDAFEFF">

	<Window.DataContext>
		<Binding Path="Main" Source="{StaticResource Locator}"/>
	</Window.DataContext>

    <Grid x:Name="LayoutRoot">

        <Label Content="Nombre y apellido:" HorizontalAlignment="Left" Margin="8,24,0,0" VerticalAlignment="Top"/>
        <TextBox Height="26.04" Margin="8,53.96,19,0" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding Nombre}"/>
        <Label Content="Fecha nacimiento (dd/mm/yyyy):" Margin="8,89,46,0" VerticalAlignment="Top" d:LayoutOverrides="Width"/>
        <TextBox Margin="8,118.96,19,121" TextWrapping="Wrap" Text="{Binding FechaNacimiento}"/>
        <Button Content="Dale" Height="28" Margin="0,0,19,83.942" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="69" Command="{Binding DaleCommand}"/>
        <TextBlock Margin="8,0,19,12" TextWrapping="Wrap" VerticalAlignment="Bottom" Height="39" FontWeight="Bold" Foreground="#FF0048D6" Text="{Binding Saludo}"/>

    </Grid>
</Window>

Nada complejo, lo justo y necesario para implementar Dependency Injection.

Empezamos con una clase Persona que aloje nuestro objeto:

namespace MVVMWithDI.Model
{
    public class Persona
    {
        public string Nombre { get; set; }
        public int Edad { get; set; }
    }
}

Ahora necesitaremos 2 servicios, uno que nos pase de “Fecha nacimiento => Edad” y otro que nos capitalice el nombre.

Como ya sabéis, todos los servicios que creemos serán interfaces, las cuales serán luego implementadas por los servicios concretos.

El conversor:

namespace MVVMWithDI.Abstract
{
    public interface IFechaAEdad
    {
        int Conversion(string fecha);
    }
}

El “capitalizador”

namespace MVVMWithDI.Abstract
{
    public interface ICapitalizador
    {
        string Capitalizar(string nombre);
    }
}

Vale, ya tenemos las interfaces, vamos con las implementaciones de esos servicios:

namespace MVVMWithDI.Concrete
{
    public class FechaAEdad : IFechaAEdad
    {
        public int Conversion(string fecha)
        {
            int edad;
            var partesfecha = fecha.Split('/');

            // Añadimos el año
            edad = DateTime.Now.Year - Int32.Parse(partesfecha[2]);

            // Comprobamos si hemos cumplido ya este año o no
            if (Int32.Parse(partesfecha[1]) < DateTime.Now.Month) // todavia no hemos cumplido
                edad -= 1; // le restamos un año pues
            if (Int32.Parse(partesfecha[1]) == DateTime.Now.Month)
                if (Int32.Parse(partesfecha[0]) > DateTime.Now.Day) // no ha llegado el dia
                    edad -= 1;

            return edad;
        }
    }
}

Algo simple, no vamos a marearnos en comprobar la entrada para simplificar el artículo.

Vamos con la implementación de la clase que capitaliza

namespace MVVMWithDI.Concrete
{
    public class Capitalizador : ICapitalizador
    {
        public string Capitalizar(string nombre)
        {
            string nombreCapitalizado = string.Empty;

            var trozoNombre = nombre.Split();

            foreach (var trozo in trozoNombre)
            {
                nombreCapitalizado += char.ToUpper(trozo[0]) + trozo.Substring(1) + " ";
            }

            return nombreCapitalizado.TrimEnd();
        }
    }
}

Otra clase simple.

Ahora toca implementar el MainViewModel

Empecemos con este código:

public class MainViewModel : ViewModelBase
{
	private readonly IFechaAEdad _fechaAEdad;
	private readonly ICapitalizador _capitalizador;

	public MainViewModel(IFechaAEdad fechaAEdad, ICapitalizador capitalizador)
	{
		_fechaAEdad = fechaAEdad;
		_capitalizador = capitalizador;
	}
}

Si recordás el artículo anterior, si un ViewModel (y cualquier clase en verdad) va a hacer uso de algún servicio, tenemos que crear un constructor el cual reciba las implementaciones de dichos servicios y lo indicamos colocando las interfaces que dichos servicios han de implementar. Nada dificil.

͉ste sería el código del viewModel completo:

    public class MainViewModel : ViewModelBase
    {
        private readonly IFechaAEdad _fechaAEdad;
        private readonly ICapitalizador _capitalizador;
        private string _nombre;
        private string _fechaNacimiento;
        private string _saludo;

        public RelayCommand DaleCommand { get; set; }

        public MainViewModel(IFechaAEdad fechaAEdad, ICapitalizador capitalizador)
        {
            _fechaAEdad = fechaAEdad;
            _capitalizador = capitalizador;

            DaleCommand = new RelayCommand(Dale);
        }

        public string Nombre
        {
            get { return _nombre; }
            set
            {
                _nombre = value;
                RaisePropertyChanged("Nombre");
            }
        }

        public string FechaNacimiento
        {
            get { return _fechaNacimiento; }
            set
            {
                _fechaNacimiento = value;
                RaisePropertyChanged("FechaNacimiento");
            }
        }

        public string Saludo
        {
            get { return _saludo; }
            set
            {
                _saludo = value;
                RaisePropertyChanged("Saludo");
            }
        }

        private void Dale()
        {
            var nombre = _capitalizador.Capitalizar(Nombre);
            var edad = _fechaAEdad.Conversion(FechaNacimiento);

            var persona = new Persona
                              {
                                  Nombre = nombre,
                                  Edad = edad
                              };
            Saludo = string.Format("Hola soy {0} y tengo {1} años", persona.Nombre, persona.Edad);
        }
    }
}

Fijaros como Dale hace uso de los servicios pero en ningun momento está el ViewModel acoplado a ninguno de esos dos servicios.

Si intentaramos compilar ahora el programa obtendríamos un error:

'MVVMWithDI.ViewModel.MainViewModel'

Normal… el ViewModelLocator está creando las instancias de los ViewModel sin proporcionar ningún argumento… Entonces, ¿Qué necesitamos? Necesitamos un contenedor que nos provea de las implementaciones que el ViewModel (o cualquier clase) nos pida. Para este programa vamos a usar Unity y como siempre usaremos NuGet para ello:

install-package Unity

Vale, ya tenemos Unity, ahora lo que necesitamos crear es una clase que comunmente se suele nombrar Bootstrapper y colocar ahí todo el código que necesitamos para echar a andar Unity:

namespace MVVMWithDI
{
    public class Bootstrapper
    {
        public IUnityContainer Container { get; set; }

        public Bootstrapper()
        {
            Container = new UnityContainer();

            ConfigureContainer();
        }

        private void ConfigureContainer()
        {
            Container.RegisterType<IFechaAEdad, FechaAEdad>();
            Container.RegisterInstance<ICapitalizador>(new Capitalizador());
            Container.RegisterType<MainViewModel>();
        }
    }
}

Una clase muy sencillita. Creamos un objeto del tipo IUnityContainer que para que nos entendamos, es el alma de la fiesta aquí. El contenedor es donde registramos todas las interfaces con sus respectivas implementaciones o simplemente registramos una clase que use servicios para que la autoresuelva el contenedor por si solo.

Por pasos:

Podemos registrar tipos usando RegisterType como véis con el caso de IFechaAEdad. Simplemente le indicamos la interfaz y la implementación concreta a usar.
También podemos registrar una instancia en vez del tipo directamente, como en el caso de ICapitalizador que estamos registrando una instancia en vez del tipo en sí. Esto es útil cuando el tipo tiene un constructor con parámetros, así podemos crear la instancia de antemano y luego meterla en el contenedor. En este caso sería mejor usar la otra forma, pero aquí estamos para enseñar maneras :)
Por último, podemos registrar directamente un tipo, en este caso estamos registrando el ViewModel. Lo hacemos por comodidad, pues si no lo hicieramos tendriamos que, por cada parámetro que recibe dicho ViewModel llamar al contenedor y decirle que nos provea de una implementación de dicho parámetro.

Vale, ya tenemos Unity funcionando bien, ahora solo necesitamos un sitio donde podamos instanciar el Bootstrapper, en este caso nos va bien en el ViewModelLocator.

Para empezar vamos a borrar la clase entera y vamos a reimplementarla de una manera más sencilla:

public class ViewModelLocator
{
	private static Bootstrapper _bootStrapper;

	static ViewModelLocator()
	{
		if (_bootStrapper == null)
			_bootStrapper = new Bootstrapper();
	}

	public MainViewModel Main
	{
		get { return _bootStrapper.Container.Resolve<MainViewModel>(); }
	}
}

Mucho más simple, ¿Verdad? Aquí estamos instanciando el Bootstrapper en el constructor, y cada vez que pidamos el MainViewModel, el contenedor de Unity lo resolverá y eso incluye los servicios que necesita para funcionar.

Por último tenemos que borrar una linea de MainWindow.xaml.cs:

Closing += (s, e) => ViewModelLocator.Cleanup();

La eliminamos pues ya no estamos haciendo uso de ese método del ViewModelLocator

Pues nada, ya está, en resumen:

Creamos las interfaces de nuestros servicios, las implementaciones de dichos servicios.
Si una clase va a usar esos servicios, pasamos las interfaces como parámetros en el constructor.
Registramos todos los servicios en el contenedor de nuestra libreria de elección.
Finalmente ejecutamos el motor de Unity para que todo funcione.

Si mañana decidimos por ejemplo cambiar la clase que capitaliza los nombres por una más avanzada (no sé, que compruebe los nombres en una base de datos y añada también tildes si hace falta) solo tendremos que ir al contenedor de Unity y cambiar la implementación de ICapitalizador por la nueva clase que hemos creado. Todo seguirá funcionando perfectamente sin tener que cambiar ninguna linea de codigo más.

Aquí te dejo el código fuente.

Hasta otra.

Tags: , , ,

2 comentarios

Dejar un comentario