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
DataContext
será null
por 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.