martes, 23 de mayo de 2017

MVVM Auto ViewModelLocator





Este post está dedicado a presentar un pequeño truco referente a la instanciación automática de ViewModels dentro del patrón MVVM.

En ocasiones, cuando trabajamos con pequeñas soluciones, no necesitamos la presencia de la clase ViewModelLocator como instanciador de clases ViewModels, porque normalmente no necesitamos guardar ninguna referencia a ninguna de ellas. En las siguientes líneas, aprenderemos a instanciar de forma automática nuestras clases ViewModel, sin tener que preocuparnos en registrarlas y crear propiedades de uso dentro de nuestro ViewModelLocator class.






Por supuesto, es completamente compatible con nuestras pruebas unitarias.

Realizaremos todo este trabajo con una clase de apoyo WPF , que contendrá principalmente dos attached properties:

Nos encontraremos con 2 casos:

  • El nombre de la vista y el nombre de la clase ViewModel, son equivalentes:





















             SameNameClass[View].xaml

               SameNameClass[ViewModel].cs



  • El nombre de la vista y el nombre de la clase ViewModel, NO son equivalentes:
























                DiferentNameView.xaml

                DiferentNameViewModel.cs           





Nombre Equivalentes (Vista y ViewModel)

En este caso solo configuraremos una propiedad en la vista:


<Window x:Class="AutoMVVMLocator.Example1View"
        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"
        xmlns:local="clr-namespace:AutoMVVMLocator"
        local:MLMVVM.IsAutomaticLocator="True"
        mc:Ignorable="d"
        Title="Example1View" Height="300" Width="300">

Parte importante:


local:MLMVVM.IsAutomaticLocator = True




En este ejemplo, la vista Example1View instancia un objeto de tipo Example1ViewModel como  DataContext Automáticamente.




Nombres NO Equivalentes (Vista y ViewModel)

En este caso, necesitaremos especificar una propiedad con el nombre de la clase ViewModel.


<Window x:Class="AutoMVVMLocator.Example2Window"
        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"
        xmlns:local="clr-namespace:AutoMVVMLocator"
        local:MLMVVM.ViewModelClassName="Example2ViewModel"
        local:MLMVVM.IsAutomaticLocator="True"
        mc:Ignorable="d"
        Title="Example2Window" Height="300" Width="396.546">

Parte importante:


local:MLMVVM.ViewModelClassName = Example2ViewModel
local:MLMVVM.IsAutomaticLocator = True



Importante: la propiedad ViewModelClassName tiene que estar definida en primer lugar.


In this example, the view Example2Window instances an Example2ViewModel object as DataContext automatically.



Clase MLMVVM

Esta clase muy simple, tiene dos AtachProperties y dos métodos privados.
Las AtachProperties, son las configuradas anteriormente:
  • IsAutomaticLocator.- Activa la instanciación automática de clases ViewModels.
  • ViewModelClassName.- Indica el nombre de la clase ViewModel a instanciar. Si el nombre de tu clase ViewModel es equivalente con el nombre de la vista, no es necesario configurar esta propiedad.


public static bool GetIsAutomaticLocator(DependencyObject obj)
{
    return (bool)obj.GetValue(IsAutomaticLocatorProperty);
}

public static void SetIsAutomaticLocator(DependencyObject obj, bool value)
{
    obj.SetValue(IsAutomaticLocatorProperty, value);
}

public static readonly DependencyProperty IsAutomaticLocatorProperty =
    DependencyProperty.RegisterAttached("IsAutomaticLocator", typeof(bool), typeof(MLMVVM), new PropertyMetadata(false, IsAutomaticLocatorChanged));

private static void IsAutomaticLocatorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var callOwner = d as FrameworkElement;

    var className = GetViewModelClassName(d);

    var userControl = GetInstanceOf(callOwner.GetType(), className);

    callOwner.DataContext = userControl;
}


public static string GetViewModelClassName(DependencyObject obj)
{
    return (string)obj.GetValue(ViewModelClassNameProperty);
}

public static void SetViewModelClassName(DependencyObject obj, string value)
{
    obj.SetValue(ViewModelClassNameProperty, value);
}


public static readonly DependencyProperty ViewModelClassNameProperty =
    DependencyProperty.RegisterAttached("ViewModelClassName", typeof(string), typeof(MLMVVM), new PropertyMetadata(null));


Los dos métodos privados contienen la mecánica de generación dinámica de tipos ViewModels:


private static object GetInstanceOf(Type dependencyPropertyType, string className)
{
    var assembly = dependencyPropertyType.Assembly; 

    var assemblyTypes = assembly.GetTypes();

    var classNameDef = GetClassName(dependencyPropertyType, className);

    var userControlType = assemblyTypes.FirstOrDefault(a => a.Name.Contains(classNameDef));

    if (userControlType == null) throw new ArgumentException($"Not exist a type {classNameDef} in the asembly {assembly.FullName}");

    var resultado = Activator.CreateInstance(userControlType);

    return resultado;
}

private static string GetClassName(Type dependencyPropertyType, string className)
{
    if (string.IsNullOrWhiteSpace(className)) return $"{dependencyPropertyType.Name}Model";

    return className;
}





Limitaciones


Si trabajas en un gran proyecto, o necesitas guardar referencias a los ViewModels intanciados, tú debes usar la clásica clase ViewModelLocar y olvidarte de este intanciador automático, ya que tendrías que realizar muchas virguerías para recuperar estas instancias.




Proyecto de Pruebas

En el Proyecto de pruebas, encontraremos código y ejemplos para los dos casos.

















Descargar de Aquí el proyecto de pruebas.