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

102

Estoy iniciando una nueva aplicación de escritorio y quiero construirla usando MVVM y WPF.

También tengo la intención de utilizar TDD.

El problema es que no sé cómo debo usar un contenedor IoC para inyectar mis dependencias en mi código de producción.

Supongamos que tengo la siguiente clase e interfaz:

public interface IStorage
{
    bool SaveFile(string content);
}

public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

Y luego tengo otra clase que tiene IStoragecomo dependencia, supongamos también que esta clase es un ViewModel o una clase empresarial ...

public class SomeViewModel
{
    private IStorage _storage;

    public SomeViewModel(IStorage storage){
        _storage = storage;
    }
}

Con esto puedo escribir fácilmente pruebas unitarias para asegurarme de que funcionan correctamente, usando simulacros, etc.

El problema es a la hora de utilizarlo en la aplicación real. Sé que debo tener un contenedor de IoC que vincule una implementación predeterminada para la IStorageinterfaz, pero ¿cómo lo haría?

Por ejemplo, ¿cómo sería si tuviera el siguiente xaml:

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

¿Cómo puedo 'decirle' correctamente a WPF que inyecte dependencias en ese caso?

Además, supongamos que necesito una instancia de SomeViewModelmi código C #, ¿cómo debo hacerlo?

Siento que estoy completamente perdido en esto, agradecería cualquier ejemplo u orientación de cómo es la mejor manera de manejarlo.

Estoy familiarizado con StructureMap, pero no soy un experto. Además, si hay un marco mejor / más fácil / listo para usar, hágamelo saber.

Fedaykin
fuente
Con .net core 3.0 en vista previa, puede hacerlo con algunos paquetes nuget de Microsoft.
Bailey Miller

Respuestas:

87

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 la IStorageinterfaz como parámetro de constructor:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Cree una ViewModelLocatorpropiedad con una obtención para el modelo de vista, que carga el modelo de vista desde Ninject:

class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

Convierta el ViewModelLocatorrecurso en una aplicación amplia en App.xaml:

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

Enlace el DataContextde UserControla la propiedad correspondiente en ViewModelLocator.

<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

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

class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

Inicialice el kernel de IoC al inicio de 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 una IocKernelclase estática para contener la instancia de toda la aplicación del kernel de IoC, por lo que puedo acceder fácilmente cuando sea necesario:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

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

Esta solución hace uso de un static ServiceLocator(the IocKernel), que generalmente se considera un anti-patró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 interfaz de usuario, ya que deben tener un constructor sin parámetros y, de todos modos, no puede controlar la instanciación, 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 localizador de servicios estáticos, al permitir que Ninject instancia clases de UI. Los detalles de la respuesta se pueden ver aquí.

Sondergard
fuente
13
Soy nuevo en la inyección de dependencias, pero en el fondo su solución es combinar el antipatrón Service Locator con Ninject, ya que está utilizando el ViewModel Locator estático. Se podría argumentar que la inyección se realiza en un archivo Xaml, que es menos probable que se pruebe. No tengo una solución mejor y probablemente usaré la suya; sin embargo, creo que sería útil mencionar esto en la respuesta también.
user3141326
Hombre de su solución es simplemente genial, sólo hay un "problema" con la siguiente línea: DataContext="{Binding [...]}". Esto hace que VS-Designer ejecute todo el código de programa en el constructor de ViewModel. En mi caso, la ventana se está ejecutando y bloquea modalmente cualquier interacción con VS. Quizás uno debería modificar el ViewModelLocator para no ubicar los ViewModels "reales" en tiempo de diseño. - Otra solución es "Desactivar el código del proyecto", que también evitará que se muestre todo lo demás. Quizás ya hayas encontrado una buena solución para esto. En este caso, le agradecería que lo mostrara.
LuckyLikey
@LuckyLikey Puede intentar usar d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" pero no estoy seguro de que haga una diferencia. Pero, ¿por qué / cómo el constructor de VM lanza una ventana modal? ¿Y qué tipo de ventana?
sondergard
@son En realidad, no sé por qué ni cómo, pero cuando abro un Diseñador de ventanas desde el Explorador de soluciones, cuando se abre la nueva pestaña, el diseñador muestra la ventana y aparece la misma ventana como si fuera modal de depuración, alojado en un nuevo proceso fuera de VS "Micorosoft Visual Studio XAML Designer". Si el proceso se cierra, VS-Designer también falla con la excepción mencionada anteriormente. Voy a probar tu solución. Te notificaré cuando detecte nueva información :)
LuckyLikey
1
@sondergard He publicado una mejora en su respuesta, evitando ServiceLocator Anti-Pattern. No dudes en comprobarlo.
LuckyLikey
52

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

Por loDataContext tanto, no puede establecer la 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 toda la jerarquía del modelo de vista cuando se inicia la aplicación (tendrá que eliminar la StartupUripropiedad del App.xamlarchivo):

public partial class App {

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

}

Esto se basa en un gráfico de objeto de modelos de vista arraigados en el, RootViewModelpero puede inyectar algunas fábricas de modelos de vista en modelos de vista principales, lo que les permite crear nuevos modelos de vista 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 SomeViewModelmi cscódigo, ¿cómo debo hacerlo?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

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

  ObservableCollection<ChildViewModel> 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 DataContextla vista en el modelo de vista. Puede hacer esta vista primero donde elige un modelo de vista basado en una vista o puede hacerlo primero el modelo de vistadonde el modelo de vista determina qué vista usar. Un marco MVVM proporciona esta funcionalidad clave 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 DataContexten 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, también puede usar atributos en tiempo de diseño en WPF. Una forma de hacerlo es agregar los siguientes atributos al <Window>elemento o <UserControl>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 datos en tiempo de diseño y otro para la inyección de dependencia:

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.

Martin Liversage
fuente
12
Esto es exactamente lo que estaba buscando. Me frustra la cantidad de veces que leo respuestas que dicen "Solo usa el marco [ yadde-ya ]". Eso está muy bien, pero primero quiero saber exactamente cómo hacerlo yo mismo y luego puedo saber qué tipo de marco podría ser realmente útil para mí. Gracias por deletrearlo tan claramente.
kmote
28

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

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

Haciendo lo StandardKerneldisponible en todas partes

La clave de la magia de Ninject es la StandardKernel-Instancia que se necesita para usar el .Get<T>()-Método.

Alternativamente a sondergard's IocContainer, puede crear el StandardKernelinterior de App-Class.

Simplemente elimine StartUpUri de su App.xaml

<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

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<MainWindow>();
        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, por ejemplo, Property Setter Injection o la más común de Constructor Injection .

Esta es la forma en que se inyecte el modelo de vista en sus Window'sDataContext

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

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

Accediendo al Kernel directamente

Si necesita llamar a Methods en el Kernel directamente (por ejemplo, .Get<T>()-Method), puede dejar que el Kernel se inyecte a sí mismo.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        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.

Cómo el Ninject.Extensions.Factory se va a utilizar se puede también leer aquí .

LuckyLikey
fuente
Buen enfoque. Nunca exploré Ninject a este nivel, pero puedo ver que me estoy perdiendo :)
sondergard
@son thx. Al final de su respuesta, dijo que si alguien tiene una mejor manera, por favor, comparta. ¿Podrías agregar un enlace a esto?
LuckyLikey
si alguien está interesado en cómo usar Ninject.Extensions.Factoryesto, indíquelo aquí en los comentarios y agregaré más información.
LuckyLikey
1
@LuckyLikey: ¿Cómo podría agregar un ViewModel a un contexto de datos de ventana a través de XAML que no tiene un constructor sin parámetros? Con la solución de sondergard con ServiceLocator esta situación sería posible.
Thomas Geulen
Entonces, por favor, dígame cómo recuperar los servicios que necesito en las propiedades adjuntas. Siempre son estáticos, tanto el DependencyPropertycampo de respaldo como sus métodos Get y Set.
springy76
12

Opto por un enfoque de "vista primero", donde paso el modelo de vista al constructor de la vista (en su código subyacente), que se asigna al contexto de datos, por ejemplo

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

Esto reemplaza su enfoque basado en XAML.

Utilizo el marco Prism para manejar la navegación: cuando algún código solicita que se muestre una vista en particular ("navegando" hacia ella), Prism resolverá esa vista (internamente, usando el marco DI de la aplicación); el marco de DI, a su vez, resolverá cualquier dependencia que tenga la vista (el modelo de vista en mi ejemplo), luego resolverá sus dependencias, y así sucesivamente.

La elección del marco DI es bastante irrelevante, ya que todos hacen esencialmente lo mismo, es decir, registra una interfaz (o un tipo) junto con el tipo concreto que desea que el marco instancia cuando encuentre una dependencia en esa interfaz. Para que conste, utilizo Castle Windsor.

La navegación por prisma requiere algo de tiempo para acostumbrarse, pero es bastante buena una vez que la entiendes, lo que te permite componer tu aplicación usando diferentes vistas. Por ejemplo, puede crear una "región" de Prisma en su ventana principal, luego, utilizando la navegación de Prisma, cambiaría de una vista a otra dentro de esta región, por ejemplo, cuando el usuario selecciona elementos del menú o lo que sea.

Alternativamente, eche un vistazo a uno de los marcos MVVM como MVVM Light. No tengo experiencia con estos, así que no puedo comentar sobre cómo se usan.

Andrew Stephens
fuente
1
¿Cómo se pasan los argumentos del constructor a las vistas secundarias? Probé este enfoque, pero obtengo excepciones en la vista principal que me dicen que la vista secundaria no tiene un constructor sin parámetros predeterminado
Doctor Jones
10

Instale MVVM Light.

Parte de la instalación consiste en crear un localizador de modelos de vista. Esta es una clase que expone sus modelos de vista como propiedades. El getter de estas propiedades luego puede devolver instancias de su motor IOC. Afortunadamente, MVVM light también incluye el marco SimpleIOC, pero puede conectar otros si lo desea.

Con IOC simple, registra una implementación contra un tipo ...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);

En este ejemplo, se crea su modelo de vista y se le pasa un objeto de proveedor de servicios según su constructor.

Luego, crea una propiedad que devuelve una instancia de IOC.

public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}

La parte inteligente es que el localizador de modelos de vista se crea en app.xaml o equivalente como fuente de datos.

<local:ViewModelLocator x:key="Vml" />

Ahora puede enlazar a su propiedad 'MyViewModel' para obtener su modelo de vista con un servicio inyectado.

Espero que ayude. Disculpas por cualquier inexactitud en el código, codificado desde la memoria en un iPad.

Kidshaw
fuente
No debe tener un programa de arranque GetInstanceo resolvefuera del programa de arranque de la aplicación. ¡Ese es el punto de DI!
Soleil - Mathieu Prévot
Estoy de acuerdo en que podría establecer el valor de la propiedad durante el inicio, pero sugerir que usar la instanciación perezosa está en contra de DI es incorrecto.
kidshaw
@kishaw No lo hice.
Soleil - Mathieu Prévot
3

Estuche Canonic DryIoc

Respondiendo a una publicación anterior, pero haciendo esto DryIocy haciendo lo que creo que es un buen uso de DI e interfaces (uso mínimo de clases concretas).

  1. El punto de partida de una aplicación WPF es App.xaml, y allí le contamos cuál es la vista inicial a utilizar; lo hacemos con código detrás en lugar del xaml predeterminado:
  2. eliminar StartupUri="MainWindow.xaml"en App.xaml
  3. en codebehind (App.xaml.cs) agregue esto override OnStartup:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DryContainer.Resolve<MainWindow>().Show();
    }

ese es el punto de inicio; también es el único lugar al que se resolvedebe llamar.

  1. la raíz de la configuración (según el libro de Mark Seeman Inyección de dependencia en .NET; el único lugar donde se deben mencionar las clases concretas) estará en el mismo código subyacente, en el constructor:

    public Container DryContainer { get; private set; }
    
    public App()
    {
        DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
        DryContainer.Register<IDatabaseManager, DatabaseManager>();
        DryContainer.Register<IJConfigReader, JConfigReader>();
        DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
            Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
        DryContainer.Register<MainWindow>();
    }

Comentarios y algunos detalles más

  • Usé clase concreta solo con la vista MainWindow;
  • Tuve que especificar qué constructor usar (tenemos que hacerlo con DryIoc) para ViewModel, porque el constructor predeterminado debe existir para el diseñador XAML, y el constructor con inyección es el que se usa para la aplicación.

El constructor ViewModel con DI:

public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
    _dbMgr = dbmgr;
    _jconfigReader = jconfigReader;
}

Constructor predeterminado de ViewModel para el diseño:

public MainWindowViewModel()
{
}

El código detrás de la vista:

public partial class MainWindow
{
    public MainWindow(IMainWindowViewModel vm)
    {
        InitializeComponent();
        ViewModel = vm;
    }

    public IViewModel ViewModel
    {
        get { return (IViewModel)DataContext; }
        set { DataContext = value; }
    }
}

y lo que se necesita en la vista (MainWindow.xaml) para obtener una instancia de diseño con ViewModel:

d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"

Conclusión

Por lo tanto, obtuvimos una implementación muy limpia y mínima de una aplicación WPF con un contenedor DryIoc y DI, al tiempo que mantuvimos posibles las instancias de diseño de vistas y modelos de vista.

Soleil - Mathieu Prévot
fuente
2

Utilice el marco de extensibilidad administrado .

[Export(typeof(IViewModel)]
public class SomeViewModel : IViewModel
{
    private IStorage _storage;

    [ImportingConstructor]
    public SomeViewModel(IStorage storage){
        _storage = storage;
    }

    public bool ProperlyInitialized { get { return _storage != null; } }
}

[Export(typeof(IStorage)]
public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

//Somewhere in your application bootstrapping...
public GetViewModel() {
     //Search all assemblies in the same directory where our dll/exe is
     string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
     var catalog = new DirectoryCatalog(currentPath);
     var container = new CompositionContainer(catalog);
     var viewModel = container.GetExport<IViewModel>();
     //Assert that MEF did as advertised
     Debug.Assert(viewModel is SomViewModel); 
     Debug.Assert(viewModel.ProperlyInitialized);
}

En general, lo que haría es tener una clase estática y usar el patrón de fábrica para proporcionarle un contenedor global (en caché, natch).

En cuanto a cómo inyectar los modelos de vista, los inyecta de la misma manera que inyecta todo lo demás. Cree un constructor de importación (o coloque una declaración de importación en una propiedad / campo) en el código subyacente del archivo XAML y dígale que importe el modelo de vista. A continuación, se unen a su Window's DataContextde esa propiedad. Los objetos raíz que usted mismo saca del contenedor suelen ser Windowobjetos compuestos . Simplemente agregue interfaces a las clases de ventana y expórtelas, luego tómelas del catálogo como se indicó anteriormente (en App.xaml.cs ... ese es el archivo de arranque de WPF).

Neologismo inteligente
fuente
Le falta un punto importante de DI que es evitar la creación de cualquier instancia new.
Soleil - Mathieu Prévot
0

Sugeriría usar ViewModel - Primer enfoque https://github.com/Caliburn-Micro/Caliburn.Micro

ver: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions

utilizar Castle Windsorcomo contenedor IOC.

Todo sobre las convenciones

Una de las principales características de Caliburn.Micro se manifiesta en su capacidad para eliminar la necesidad de un código de placa de caldera actuando sobre una serie de convenciones. Algunas personas aman las convenciones y otras las odian. Es por eso que las convenciones de CM son totalmente personalizables e incluso se pueden desactivar por completo si no se desea. Si va a utilizar convenciones, y dado que están activadas de forma predeterminada, es bueno saber cuáles son esas convenciones y cómo funcionan. Ese es el tema de este artículo. Ver resolución (ViewModel-First)

Lo esencial

La primera convención que probablemente encontrará al utilizar CM está relacionada con la resolución de la vista. Esta convención afecta a cualquier área de ViewModel-First de su aplicación. En ViewModel-First, tenemos un ViewModel existente que necesitamos renderizar en la pantalla. Para hacer esto, CM usa un patrón de nomenclatura simple para encontrar un UserControl1 que debería enlazar con ViewModel y mostrar. Entonces, ¿cuál es ese patrón? Echemos un vistazo a ViewLocator.LocateForModelType para descubrir:

public static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) =>{
    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if(context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
        viewTypeName = viewTypeName + "." + context;
    }

    var viewType = (from assmebly in AssemblySource.Instance
                    from type in assmebly.GetExportedTypes()
                    where type.FullName == viewTypeName
                    select type).FirstOrDefault();

    return viewType == null
        ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
        : GetOrCreateViewType(viewType);
};

Ignoremos la variable de "contexto" al principio. Para derivar la vista, asumimos que está utilizando el texto "ViewModel" en el nombre de sus VM, por lo que simplemente lo cambiamos a "Ver" en todos los lugares donde lo encontremos eliminando la palabra "Modelo". Esto tiene el efecto de cambiar tanto los nombres de tipos como los espacios de nombres. Entonces ViewModels.CustomerViewModel se convertiría en Views.CustomerView. O si está organizando su aplicación por función: CustomerManagement.CustomerViewModel se convierte en CustomerManagement.CustomerView. Con suerte, eso es bastante sencillo. Una vez que tenemos el nombre, buscamos tipos con ese nombre. Buscamos cualquier ensamblado que haya expuesto a CM como buscable a través de AssemblySource.Instance.2 Si encontramos el tipo, creamos una instancia (u obtenemos una del contenedor de IoC si está registrado) y se la devolvemos al llamador. Si no encontramos el tipo,

Ahora, volvamos a ese valor de "contexto". Así es como CM admite múltiples vistas sobre el mismo modelo de vista. Si se proporciona un contexto (normalmente una cadena o una enumeración), hacemos una transformación adicional del nombre, en función de ese valor. Esta transformación asume efectivamente que tiene una carpeta (espacio de nombres) para las diferentes vistas al eliminar la palabra "Vista" del final y agregar el contexto en su lugar. Entonces, dado un contexto de "Maestro", nuestros ViewModels.CustomerViewModel se convertirían en Views.Customer.Master.

Nahum
fuente
2
Toda tu publicación es una opinión.
John Peters
-1

Elimina la uri de inicio de tu app.xaml.

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IoC.Configure(true);

        StartupUri = new Uri("Views/MainWindowView.xaml", UriKind.Relative);

        base.OnStartup(e);
    }
}

Ahora puede usar su clase de IoC para construir las instancias.

MainWindowView.xaml.cs

public partial class MainWindowView
{
    public MainWindowView()
    {
        var mainWindowViewModel = IoC.GetInstance<IMainWindowViewModel>();

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}
C Bauer
fuente
No debería tener ningún contenedor GetInstancede resolveapp.xaml.cs externo, está perdiendo el punto de DI. Además, mencionar la vista xaml en el código subyacente de la vista es algo complicado. Simplemente llame a la vista en c # puro y haga esto con el contenedor.
Soleil - Mathieu Prévot