Saltar al contenido

Cómo manejar la inyección de dependencia en una aplicación WPF / MVVM

Basta ya de buscar en otras webs ya que llegaste al lugar perfecto, contamos con la respuesta que quieres encontrar pero sin problema.

Solución:

He estado usando Ninject y descubrí que es un placer trabajar con él. Todo está configurado en código, la sintaxis es bastante sencilla y tiene una buena documentación (y muchas respuestas sobre SO).

Entonces, básicamente, es así:

Cree el modelo de vista y tome el IStorage interfaz como parámetro de constructor:

class UserControlViewModel

    public UserControlViewModel(IStorage storage)
    

    

Crear un ViewModelLocator con una propiedad get para el modelo de vista, que carga el modelo de vista desde Ninject:

class ViewModelLocator

    public UserControlViewModel UserControlViewModel
    
        get  return IocKernel.Get(); // Loading UserControlViewModel will automatically load the binding for IStorage
    

Hacer el ViewModelLocator un recurso de toda la aplicación en App.xaml:


    
        
    

Unir el DataContext de El UserControl a la propiedad correspondiente en ViewModelLocator.


    
    

Cree una clase heredando NinjectModule, que configurará los enlaces necesarios (IStorage y el modelo de vista):

class IocConfiguration : NinjectModule

    public override void Load()
    
        Bind().To().InSingletonScope(); // Reuse same storage every time

        Bind().ToSelf().InTransientScope(); // Create new instance every time
    

Inicialice el kernel de IoC al iniciar la aplicación con los módulos Ninject necesarios (el de arriba por ahora):

public partial class App : Application
       
    protected override void OnStartup(StartupEventArgs e)
    
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    

He usado un static IocKernel class para contener la instancia de toda la aplicación del kernel de IoC, de modo que pueda acceder fácilmente cuando sea necesario:

public static class IocKernel

    private static StandardKernel _kernel;

    public static T Get()
    
        return _kernel.Get();
    

    public static void Initialize(params INinjectModule[] modules)
    
        if (_kernel == null)
        
            _kernel = new StandardKernel(modules);
        
    

Esta solución hace uso de un static ServiceLocator (los IocKernel), que generalmente se considera un antipatrón, porque oculta las dependencias de la clase. Sin embargo, es muy difícil evitar algún tipo de búsqueda de servicio manual para las clases de IU, ya que deben tener un constructor sin parámetros y no puede controlar la instanciación de todos modos, por lo que no puede inyectar la VM. Al menos de esta manera, le permite probar la máquina virtual de forma aislada, que es donde está toda la lógica empresarial.

Si alguien tiene una mejor manera, por favor comparta.

EDITAR: Lucky Likey proporcionó una respuesta para deshacerse del static localizador de servicios, al permitir que Ninject cree una instancia de las clases de interfaz de usuario. Los detalles de la respuesta se pueden ver aquí.

En su pregunta, establece el valor de la DataContext propiedad de la vista en XAML. Esto requiere que su modelo de vista tenga un constructor predeterminado. Sin embargo, como ha notado, esto no funciona bien con la inyección de dependencias donde desea inyectar dependencias en el constructor.

Entonces no puedes configurar el DataContext propiedad en XAML. En su lugar, tiene otras alternativas.

Si su aplicación se basa en un modelo de vista jerárquico simple, puede construir la jerarquía completa del modelo de vista cuando se inicia la aplicación (tendrá que eliminar el StartupUri propiedad de la App.xaml expediente):

public partial class App 

  protected override void OnStartup(StartupEventArgs e) 
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve();
    var window = new MainWindow  DataContext = viewModel ;
    window.Show();
  


Esto se basa en un gráfico de objetos de modelos de vista arraigados en el RootViewModel pero puede inyectar algunas fábricas de modelos de vistas en modelos de vistas principales, lo que les permite crear nuevos modelos de vistas secundarios para que el gráfico de objetos no tenga que ser arreglado. Con suerte, esto también responde a su pregunta. supongamos que necesito una instancia de SomeViewModel de mi cs código, ¿cómo debo hacerlo?

class ParentViewModel 

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) 
    _childViewModelFactory = childViewModelFactory;
  

  public void AddChild() 
    Children.Add(_childViewModelFactory.Create());
  

  ObservableCollection Children  get; private set; 

 

class ChildViewModelFactory 

  public ChildViewModelFactory(/* ChildViewModel dependencies */) 
    // Store dependencies.
  

  public ChildViewModel Create() 
    return new ChildViewModel(/* Use stored dependencies */);
  


Si su aplicación es de naturaleza más dinámica y quizás se basa en la navegación, tendrá que conectarse al código que realiza la navegación. Cada vez que navega a una nueva vista, necesita crear un modelo de vista (desde el contenedor DI), la vista en sí y establecer el DataContext de la vista al modelo de vista. Puedes hacerlo ver primero donde eliges un modelo de vista basado en una vista o puedes hacerlo modelo de vista primero donde el modelo de vista determina qué vista usar. Un marco MVVM proporciona esto key funcionalidad con alguna forma de conectar su contenedor DI en la creación de modelos de vista, pero también puede implementarlo usted mismo. Soy un poco vago aquí porque, dependiendo de sus necesidades, esta funcionalidad puede volverse bastante compleja. Esta es una de las funciones principales que obtiene de un marco MVVM, pero implementar la suya propia en una aplicación simple le dará una buena comprensión de lo que proporcionan los marcos MVVM bajo el capó.

Al no poder declarar la DataContext en XAML pierde algo de compatibilidad en tiempo de diseño. Si su modelo de vista contiene algunos datos, aparecerá durante el tiempo de diseño, lo que puede ser muy útil. Afortunadamente, puede utilizar el tiempo de diseño attributes también en WPF. Una forma de hacer esto es agregar lo siguiente attributes al elemento o en XAML:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True"

El tipo de modelo de vista debe tener dos constructores, el predeterminado para los datos en tiempo de diseño y otro para la inyección de dependencias:

class MyViewModel : INotifyPropertyChanged 

  public MyViewModel() 
    // Create some design-time data.
  

  public MyViewModel(/* Dependencies */) 
    // Store dependencies.
  


Al hacer esto, puede usar la inyección de dependencia y mantener un buen soporte en tiempo de diseño.

Lo que estoy publicando aquí es una mejora de la respuesta de sondergard, porque lo que voy a contar no encaja en un comentario 🙂

De hecho, estoy presentando una solución ordenada, que evita la necesidad de un ServiceLocator y un envoltorio para el StandardKernel-Instance, que en la solución de Sondergard se llama IocContainer. ¿Por qué? Como se mencionó, esos son anti-patrones.

Haciendo el StandardKernel disponible en todas partes

La clave de la magia de Ninject es la StandardKernel-Instancia necesaria para utilizar el .Get()-Método.

Alternativamente a sondergard’s IocContainer puedes crear el StandardKernel dentro de App-Clase.

Simplemente elimine StartUpUri de su App.xaml


             ... 

Este es el CodeBehind de la aplicación dentro de App.xaml.cs

public partial class App

    private IKernel _iocKernel;

    protected override void OnStartup(StartupEventArgs e)
    
        base.OnStartup(e);

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get();
        Current.MainWindow.Show();
    

A partir de ahora, Ninject está vivo y listo para luchar 🙂

Inyectando tu DataContext

Como Ninject está vivo, puede realizar todo tipo de inyecciones, p. Ej. Inyección de Property Setter o el más común Inyección de constructor.

Así es como inyecta su ViewModel en su Window‘s DataContext

public partial class MainWindow : Window

    public MainWindow(MainWindowViewModel vm)
    
        DataContext = vm;
        InitializeComponent();
    

Por supuesto, también puede inyectar un IViewModel si hace las vinculaciones correctas, pero eso no es parte de esta respuesta.

Accediendo al Kernel directamente

Si necesita llamar a Methods en el Kernel directamente (p. Ej. .Get()-Método), puede dejar que el núcleo se inyecte.

    private void DoStuffWithKernel(IKernel kernel)
    
        kernel.Get();
        kernel.Whatever();
    

Si necesita una instancia local del Kernel, puede inyectarla como Propiedad.

    [Inject]
    public IKernel Kernel  private get; set; 

Aunque esto puede ser bastante útil, no te recomendaría que lo hicieras. Solo tenga en cuenta que los objetos inyectados de esta manera no estarán disponibles dentro del Constructor, porque se inyecta más tarde.

De acuerdo con este enlace, debe usar la extensión de fábrica en lugar de inyectar el IKernel (Contenedor DI).

El enfoque recomendado para emplear un contenedor DI en un sistema de software es que la Raíz de composición de la aplicación sea el único lugar donde se toca directamente el contenedor.

La forma en que se utilizará Ninject.Extensions.Factory también puede aparecer en rojo aquí.

Si posees alguna sospecha o capacidad de regenerar nuestro post puedes añadir una explicación y con gusto lo ojearemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *