wpf - ¿Qué es un ViewModelLocator y cuáles son sus ventajas y desventajas en comparación con DataTemplates?
mvvm mvvm-light (3)
Introducción
En MVVM, la práctica habitual es hacer que las Vistas encuentren sus Modelos de Vista resolviéndolos desde un contenedor de inyección de dependencia (DI). Esto ocurre automáticamente cuando se solicita 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 de 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 al ordenarle que resuelva los tipos (vistas y modelos de vista) que usamos cuando realmente se ejecuta nuestra aplicación, pero dándole instrucciones de manera diferente cuando se ejecutan las pruebas unitarias para la aplicación. En este último caso, la aplicación ni siquiera tendrá una IU (no se está ejecutando, solo las pruebas), por lo que el contenedor resolverá los mocks en lugar de los tipos "normales" utilizados 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 los 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 de aplicaciones normales como en las ejecuciones de pruebas unitarias, alguien tiene que configurar el contenedor con instrucciones sobre qué tipos resolver; adicionalmente, alguien tiene que pedir al contenedor que resuelva las Vistas para que se puedan inyectar ViewModels en ellas.
Sin embargo, en tiempo de diseño no hay código nuestro en ejecución . El diseñador intenta usar la reflexión para crear instancias de nuestras Vistas, lo que significa que:
- Si el constructor de View requiere una instancia de ViewModel, el diseñador no podrá instanciar la View en absoluto; se producirá un error de forma controlada.
- Si la Vista tiene un constructor sin parámetros, la Vista se creará una instancia, pero su
DataContext
seránull
por lo que obtendremos una vista "vacía" en el diseñador, lo cual no es muy útil.
Ingrese ViewModelLocator
ViewModelLocator es una abstracción adicional utilizada así:
- La vista en sí crea una instancia de ViewModelLocator como parte de sus resources y databinds DataContext a la propiedad ViewModel del localizador
- El localizador de alguna manera detecta si estamos en modo de diseño
- Si no está en modo de diseño, el localizador devuelve un ViewModel que se resuelve del contenedor DI, como se explicó anteriormente.
- Si está en modo de diseño, el localizador devuelve un modelo de vista "ficticio" fijo utilizando su propia lógica (recuerde: ¡no hay contenedor en tiempo de diseño!); este ViewModel típicamente viene prepoblado 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 y, a la vez, permite que su código se reproduzca bien con los diseñadores visuales. Esto a veces se denomina "capacidad de mezcla" de su aplicación (haciendo referencia a Expression Blend).
Después de digerir lo anterior, vea un ejemplo práctico here .
Finalmente, el uso de plantillas de datos no es una alternativa al uso de ViewModelLocator, sino una alternativa al uso de pares explícitos de View / ViewModel para partes de su UI. A menudo, puede descubrir que no es necesario definir una Vista para un modelo de vista porque puede usar una plantilla de datos.
¿Puede alguien darme un resumen rápido de lo que es un ViewModelLocator, cómo funciona y cuáles son los pros / contras para usarlo en comparación con DataTemplates?
He intentado encontrar información en Google, pero parece que hay muchas implementaciones diferentes de la misma y no hay una lista detallada de lo que es y las ventajas y desventajas de usarlo.
No entiendo por qué las otras respuestas de esta pregunta envuelven al Diseñador.
El propósito del Localizador de modelos de vista es permitir que su vista cree una instancia de esto (sí, ver el localizador de modelos = ver primero):
public void MyWindowViewModel(IService someService)
{
}
en lugar de solo esto:
public void MyWindowViewModel()
{
}
al declarar esto:
DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"
Donde ViewModelLocator
es clase, que hace referencia a un IoC y así es como resuelve la propiedad MainWindowModel
que expone.
No tiene nada que ver con el suministro de modelos de vista simulada a su vista. Si quieres eso, solo hazlo
d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"
El View Model Locator es un contenedor alrededor de algún (cualquier) Inversión del contenedor de Control, como Unity, por ejemplo.
Referirse a:
Un ejemplo de implementación de la respuesta de @ Jon
Tengo una clase de localizador de modelos de vista. Cada propiedad va a ser una instancia del modelo de vista que voy a asignar en mi vista. Puedo verificar si el código se está ejecutando en modo de diseño o si no uso DesignerProperties.GetIsInDesignMode
. Esto me permite usar un modelo simulado durante el diseño del tiempo y el objeto real cuando estoy ejecutando 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 recursos de App.xaml
:
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 viewmodel:
<Window ...
DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">