¿Qué es un ViewModelLocator y cuáles son sus pros / contras en comparación con DataTemplates?

112

¿Alguien puede darme un resumen rápido de lo que es un ViewModelLocator, cómo funciona y cuáles son las ventajas / desventajas de usarlo en comparación con las plantillas de datos?

Intenté encontrar información en Google, pero parece que hay muchas implementaciones diferentes y no hay una lista estricta sobre lo que es y los pros / contras de usarlo.

Raquel
fuente

Respuestas:

204

Intro

En MVVM, la práctica habitual es que las Vistas encuentren sus ViewModels resolviéndolos desde un contenedor de inyección de dependencia (DI). Esto sucede automáticamente cuando se le pide al contenedor que proporcione (resuelva) una instancia de la clase View. El contenedor inyecta ViewModel en la Vista llamando a un constructor de la Vista que acepta un parámetro ViewModel; este esquema se llama inversión de control (IoC).

Beneficios de DI

El principal beneficio aquí es que el contenedor se puede configurar en tiempo de ejecución con instrucciones sobre cómo resolver los tipos que le solicitamos. Esto permite una mayor capacidad de prueba indicándole que resuelva los tipos (Vistas y ViewModels) que usamos cuando nuestra aplicación realmente se ejecuta, pero instruyéndolo de manera diferente cuando ejecuta las pruebas unitarias para la aplicación. En el último caso, la aplicación ni siquiera tendrá una interfaz de usuario (no se está ejecutando, solo las pruebas), por lo que el contenedor resolverá las simulaciones en lugar de los tipos "normales" que se usan cuando se ejecuta la aplicación.

Problemas derivados de DI

Hasta ahora, hemos visto que el enfoque DI permite una fácil prueba de la aplicación al agregar una capa de abstracción sobre la creación de componentes de la aplicación. Hay un problema con este enfoque: no funciona bien con diseñadores visuales como Microsoft Expression Blend.

El problema es que tanto en las ejecuciones normales de la aplicación como en las pruebas unitarias, alguien tiene que configurar el contenedor con instrucciones sobre qué tipos resolver; además, alguien tiene que pedirle al contenedor que resuelva las Vistas para que los ViewModels puedan inyectarse en ellas.

Sin embargo, en tiempo de diseño no hay ningún código nuestro en ejecución . El diseñador intenta utilizar la reflexión para crear instancias de nuestras Vistas, lo que significa que:

  • Si el constructor de vistas requiere una instancia de ViewModel, el diseñador no podrá crear una instancia de la vista en absoluto; se producirá un error de forma controlada.
  • Si la vista tiene un constructor sin parámetros de la vista se crea una instancia, pero su DataContextserá nullpor lo que obtendrá una vista 'vacío' en el diseñador - que no es muy útil

Ingrese ViewModelLocator

ViewModelLocator es una abstracción adicional que se usa así:

  • La propia vista crea una instancia de ViewModelLocator como parte de sus recursos y vincula su DataContext a la propiedad ViewModel del localizador
  • El localizador detecta de alguna manera si estamos en modo diseño
  • Si no está en modo de diseño, el localizador devuelve un ViewModel que resuelve desde el contenedor DI, como se explicó anteriormente.
  • Si está en modo de diseño, el localizador devuelve un ViewModel "ficticio" fijo usando su propia lógica (recuerde: ¡no hay contenedor en tiempo de diseño!); este ViewModel normalmente viene rellenado previamente con datos ficticios

Por supuesto, esto significa que la Vista debe tener un constructor sin parámetros para empezar (de lo contrario, el diseñador no podrá instanciarlo).

Resumen

ViewModelLocator es un modismo que le permite mantener los beneficios de DI en su aplicación MVVM al mismo tiempo que permite que su código funcione bien con los diseñadores visuales. A esto a veces se le llama la "capacidad de mezcla" de su aplicación (refiriéndose a Expression Blend).

Después de digerir lo anterior, vea un ejemplo práctico aquí .

Por último, el uso de plantillas de datos no es una alternativa al uso de ViewModelLocator, sino una alternativa al uso de pares View / ViewModel explícitos para partes de su interfaz de usuario. A menudo, puede encontrar que no es necesario definir una vista para un modelo de vista porque puede usar una plantilla de datos en su lugar.

Jon
fuente
4
+1 para una gran explicación. ¿Puede ampliar más la vista y sus recursos? Por recursos, ¿te refieres a las propiedades de la vista? ¿O? ¿Tiene un enlace con un ejemplo concreto de este patrón?
Metro Smurf
@MetroSmurf: Su enlace está en la sección Resumen.
Jon
1
Gracias. ¿Existe alguna limitación para usar un ViewModelLocator? Tenía algunas preocupaciones sobre el hecho de que hacía referencia a un recurso estático: ¿se pueden crear ViewModels dinámicamente en tiempo de ejecución? ¿Y hay mucho código adicional involucrado en conectar uno?
Rachel
@Rachel: Ese mismo enlace en el Resumen debería responder estas preguntas con ejemplos prácticos.
Jon
2
Respuesta extremadamente engañosa. El propósito principal de View Model Locator no es proporcionar datos ficticios al diseñador. Puede hacer esto fácilmente especificando d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}". El propósito del localizador es habilitar DI en las vistas, porque WPF es tan malo para proporcionarlo. Ejemplo: tiene una ventana principal que abre una ventana de diálogo. Para resolver la DI en la ventana de diálogo de la forma habitual, ¡necesitaría pasarla como una dependencia de la ventana principal! Esto se evita con el localizador de vistas.
hyankov
10

Una implementación de ejemplo de la respuesta de @ Jon

Tengo una clase de localizador de modelos de vista. Cada propiedad será una instancia del modelo de vista que voy a asignar a mi vista. Puedo comprobar si el código se está ejecutando en modo de diseño o no DesignerProperties.GetIsInDesignMode. Esto me permite usar un modelo simulado durante el diseño y el objeto real cuando ejecuto la aplicación.

public class ViewModelLocator
{
    private DependencyObject dummy = new DependencyObject();

    public IMainViewModel MainViewModel
    {
        get
        {
            if (IsInDesignMode())
            {
                return new MockMainViewModel();
            }

            return MyIoC.Container.GetExportedValue<IMainViewModel>();
        }
    }

    // returns true if editing .xaml file in VS for example
    private bool IsInDesignMode()
    {
        return DesignerProperties.GetIsInDesignMode(dummy);
    }
}

Y para usarlo puedo agregar mi localizador a los App.xamlrecursos:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

<Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>

Y luego para conectar su vista (por ejemplo: MainView.xaml) a su modelo de vista:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
BrunoLM
fuente
¿Hay alguna diferencia en usar en thislugar de dummy?
Sebastian Xawery Wiśniowiecki
5

No entiendo por qué las otras respuestas de esta pregunta giran en torno al Diseñador.

El propósito del Localizador de modelos de vista es permitir que su Vista cree una instancia de esto (sí, Localizador de modelos de vista = Ver primero):

public void MyWindowViewModel(IService someService)
{
}

en lugar de solo esto:

public void MyWindowViewModel()
{
}

declarando esto:

DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

Dónde ViewModelLocatorestá la clase, que hace referencia a un IoC y así es como resuelve la MainWindowModelpropiedad que expone.

No tiene nada que ver con proporcionar modelos de vista simulada a su vista. Si quieres eso, hazlo

d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"

El Localizador de modelos de vista es un envoltorio de algún (cualquier) contenedor de Inversión de control, como Unity, por ejemplo.

Referirse a:

Hyankov
fuente
Un localizador de modelos de vista no requiere un contenedor, el usuario decide cómo se resuelve el modelo de vista a través de la configuración y usted puede usar un contenedor o simplemente crear un tipo nuevo usted mismo. Por lo tanto, puede realizar la ubicación del modelo de vista basada en convenciones, por ejemplo, en lugar de registrar previamente todas sus vistas y modelos de vista en algún contenedor.
Chris Bordeman
Tiene razón cuando dice " No entiendo por qué las otras respuestas [...] envuelven al Diseñador " +1, pero el objetivo del localizador es eliminar de la vista cualquier conocimiento de cómo se está viendo el modelo de vista creado, haciendo que la vista sea independiente de esta instanciación, dejándolo al localizador. El localizador podrá proporcionar diferentes sabores del modelo de vista, tal vez algunos personalizados agregados a través de complementos que administrará el localizador (y uno específico para el tiempo de diseño). La vista estará limpia de cualquier proceso de localización de la versión correcta del modelo de vista, eso es realmente bueno para SoC.
minutos