Como lidar com a injeção de dependência em um aplicativo WPF / MVVM


102

Estou iniciando um novo aplicativo de desktop e quero criá-lo usando MVVM e WPF.

Também pretendo usar TDD.

O problema é que não sei como devo usar um contêiner IoC para injetar minhas dependências no meu código de produção.

Suponha que eu tenha a seguinte classe e interface:

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

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

E então eu tenho outra classe que tem IStoragecomo dependência, suponha também que essa classe seja um ViewModel ou uma classe de negócios ...

public class SomeViewModel
{
    private IStorage _storage;

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

Com isso, posso escrever facilmente testes de unidade para garantir que estão funcionando corretamente, usando simulações e etc.

O problema é quando se trata de usá-lo no aplicativo real. Eu sei que devo ter um contêiner IoC que vincula uma implementação padrão para a IStorageinterface, mas como eu faria isso?

Por exemplo, como seria se eu tivesse o seguinte xaml:

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

Como posso 'dizer' corretamente ao WPF para injetar dependências nesse caso?

Além disso, suponha que eu precise de uma instância de SomeViewModeldo meu código C #, como devo fazer isso?

Sinto que estou completamente perdido nisso, agradeceria qualquer exemplo ou orientação de como é a melhor maneira de lidar com isso.

Estou familiarizado com o StructureMap, mas não sou um especialista. Além disso, se houver uma estrutura melhor / mais fácil / out-of-the-box, por favor me avise.


Com .net core 3.0 em visualização, você pode fazer isso com alguns pacotes nuget da Microsoft.
Bailey Miller

Respostas:


87

Tenho usado o Ninject e descobri que é um prazer trabalhar com ele. Tudo é configurado em código, a sintaxe é bastante direta e tem uma boa documentação (e muitas respostas sobre SO).

Então, basicamente é assim:

Crie o modelo de visualização e use a IStorageinterface como parâmetro do construtor:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Crie um ViewModelLocatorcom uma propriedade get para o modelo de visualização, que carrega o modelo de visualização do Ninject:

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

Faça ViewModelLocatorum recurso para todo o aplicativo em App.xaml:

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

Vincule o DataContextde UserControlà propriedade correspondente no ViewModelLocator.

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

Crie uma classe herdando NinjectModule, que irá configurar as ligações necessárias ( IStoragee o modelo de visualização):

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
    }
}

Inicialize o kernel IoC na inicialização do aplicativo com os módulos Ninject necessários (o que está acima por enquanto):

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

        base.OnStartup(e);
    }
}

Usei uma IocKernelclasse estática para manter a instância ampla do aplicativo do kernel IoC, para que possa acessá-la facilmente quando necessário:

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);
        }
    }
}

Essa solução faz uso de um estático ServiceLocator(o IocKernel), que geralmente é considerado um antipadrão, porque oculta as dependências da classe. No entanto, é muito difícil evitar algum tipo de consulta de serviço manual para classes de IU, uma vez que elas devem ter um construtor sem parâmetros e você não pode controlar a instanciação de qualquer maneira, portanto, não pode injetar a VM. Pelo menos essa maneira permite que você teste a VM isoladamente, que é onde está toda a lógica de negócios.

Se alguém tiver uma maneira melhor, por favor, compartilhe.

EDIT: Lucky Likey forneceu uma resposta para se livrar do localizador de serviço estático, permitindo que o Ninject instancie classes de IU. Os detalhes da resposta podem ser vistos aqui


13
Eu sou novo na injeção de dependência, mas em seu cerne sua solução é combinar o antipadrão do Service Locator com o Ninject, já que você está usando o ViewModel Locator estático. Alguém poderia argumentar que a injeção é feita no arquivo Xaml, que é menos provável de ser testado. Não tenho uma solução melhor e provavelmente usarei a sua - mas acho que seria útil mencionar isso na resposta também.
user3141326

Man sua solução é apenas grande, há apenas um "problema" com a seguinte linha: DataContext="{Binding [...]}". Isso está fazendo com que o VS-Designer execute todo o Código de Programa no Construtor do ViewModel. No meu caso, a janela está sendo executada e bloqueia modalmente qualquer interação com o VS. Talvez seja necessário modificar o ViewModelLocator para não localizar os ViewModels "reais" em tempo de design. - Outra solução é “Desabilitar Código do Projeto”, o que também impedirá que todo o resto seja mostrado. Talvez você já tenha encontrado uma solução legal para isso. Neste caso, gostaria que o mostrasse.
LuckyLikey de

@LuckyLikey Você pode tentar usar d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}", mas não tenho certeza se faz diferença. Mas por que / como o construtor VM está iniciando uma janela modal? E que tipo de janela?
sondergard de

@son Na verdade não sei por que e como, mas quando abro um Window Designer no Solution Explorer, conforme a nova aba está sendo aberta, a janela está sendo exibida pelo designer e a mesma janela aparece como se fosse o modal de depuração, hospedado em um novo processo fora do VS "Micorosoft Visual Studio XAML Designer". Se o processo for encerrado, o VS-Designer também falhará com a exceção mencionada anteriormente. Vou tentar a sua solução alternativa. Notificarei você assim que detectar novas informações :)
LuckyLikey

1
@sondergard Publiquei uma melhoria em sua resposta, evitando o ServiceLocator Anti-Pattern. Sinta-se à vontade para conferir.
LuckyLikey

52

Em sua pergunta, você definiu o valor da DataContextpropriedade da exibição em XAML. Isso requer que seu modelo de visualização tenha um construtor padrão. No entanto, como você observou, isso não funciona bem com injeção de dependência, onde você deseja injetar dependências no construtor.

Portanto, você não pode definir a DataContextpropriedade em XAML . Em vez disso, você tem outras alternativas.

Se seu aplicativo é baseado em um modelo de visualização hierárquico simples, você pode construir toda a hierarquia do modelo de visualização quando o aplicativo é iniciado (você terá que remover a StartupUripropriedade do App.xamlarquivo):

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();
  }

}

Isso se baseia em um gráfico de objeto de modelos de visão enraizados no, RootViewModelmas você pode injetar algumas fábricas de modelo de visão em modelos de visão pai, permitindo-lhes criar novos modelos de visão filho, de forma que o gráfico de objeto não precise ser corrigido. Espero que isso também responda à sua pergunta. Suponha que eu precise de uma instância de SomeViewModeldo meu cscódigo, como devo fazer isso?

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 */);
  }

}

Se o seu aplicativo for de natureza mais dinâmica e talvez se baseie na navegação, você terá que se conectar ao código que realiza a navegação. Cada vez que você navegar para uma nova visão, você precisa criar um modelo de visão (a partir do contêiner DI), a visão em si e definir o DataContextda visão para o modelo de visão. Você pode fazer esta vista primeiro, onde você escolhe um modelo de vista com base em uma vista ou você pode fazer isso primeiroonde o modelo de visão determina qual visão usar. Uma estrutura MVVM fornece essa funcionalidade chave com alguma maneira de conectar seu contêiner de DI na criação de modelos de visualização, mas você também pode implementá-lo sozinho. Estou um pouco vago aqui porque, dependendo de suas necessidades, essa funcionalidade pode se tornar bastante complexa. Esta é uma das funções principais que você obtém de uma estrutura MVVM, mas rodar a sua própria em um aplicativo simples lhe dará uma boa compreensão do que as estruturas MVVM fornecem nos bastidores.

Por não ser capaz de declarar DataContextem XAML, você perde algum suporte em tempo de design. Se o seu modelo de visualização contém alguns dados, ele aparecerá durante o tempo de design, o que pode ser muito útil. Felizmente, você pode usar atributos de tempo de design também no WPF. Uma maneira de fazer isso é adicionar os seguintes atributos ao <Window>elemento ou <UserControl>em 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}"

O tipo de modelo de visualização deve ter dois construtores, o padrão para dados em tempo de design e outro para injeção de dependência:

class MyViewModel : INotifyPropertyChanged {

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

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

}

Fazendo isso, você pode usar injeção de dependência e manter um bom suporte em tempo de design.


12
Isso é EXATAMENTE o que eu estava procurando. Fico frustrado com a quantidade de vezes que leio respostas que dizem "Basta usar a estrutura [ yadde-ya ]". Está tudo muito bem, mas eu quero saber exatamente como fazer isso sozinho primeiro e então posso saber que tipo de estrutura pode realmente ser útil para mim. Obrigado por soletrar tão claramente.
kmote de

28

O que estou postando aqui é uma melhoria da resposta de sondergard, porque o que vou contar não se encaixa em um comentário :)

Na verdade, estou apresentando uma solução bacana, que evita a necessidade de um ServiceLocator e um invólucro para a StandardKernel-Instance, que na solução de sondergard é chamada IocContainer. Por quê? Como mencionado, esses são anti-padrões.

Disponibilizando em StandardKernelqualquer lugar

A chave para a magia do Ninject é a StandardKernel-Instância necessária para usar o .Get<T>()-Método.

Como alternativa ao sondergard, IocContainervocê pode criar o StandardKerneldentro da App-Class.

Apenas remova StartUpUri do seu 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 é o CodeBehind do aplicativo 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 agora, Ninject está vivo e pronto para lutar :)

Injetando seu DataContext

Como o Ninject está vivo, você pode realizar todos os tipos de injeções, por exemplo, Property Setter Injection ou a mais comum Constructor Injection .

Isto é como você injetar seu ViewModel em seus Window'sDataContext

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

Claro que você também pode injetar um IViewModelse fizer as ligações corretas, mas isso não faz parte desta resposta.

Acessando o kernel diretamente

Se você precisar chamar métodos diretamente no Kernel (por exemplo, .Get<T>()-Method), você pode deixar o Kernel se injetar.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

Se você precisar de uma instância local do Kernel, pode injetá-la como Propriedade.

    [Inject]
    public IKernel Kernel { private get; set; }

Embora isso possa ser muito útil, eu não recomendo que você faça isso. Observe que os objetos injetados desta forma, não estarão disponíveis dentro do Construtor, pois é injetado posteriormente.

De acordo com este link você deve usar o ramal de fábrica ao invés de injetar o IKernel(DI Container).

A abordagem recomendada para empregar um contêiner de DI em um sistema de software é que a Raiz de Composição do aplicativo seja o único lugar onde o contêiner é tocado diretamente.

A forma como a Ninject.Extensions.Factory deve ser usada também pode ser exibida em vermelho aqui .


Boa abordagem. Nunca explorei Ninject a este nível, mas posso ver que estou perdendo :)
sondergard

@son thx. No final da sua resposta, você afirmou: Se alguém tiver uma maneira melhor, compartilhe. Você pode adicionar um link a isso?
LuckyLikey

se alguém estiver interessado em como usar Ninject.Extensions.Factorynisso, diga aqui nos comentários e eu adicionarei mais algumas informações.
LuckyLikey

1
@LuckyLikey: Como seria capaz de adicionar um ViewModel a um datacontext de janela via XAML que não tem um construtor sem parâmetros? Com a solução da sondergard com o ServiceLocator esta situação seria possível.
Thomas Geulen 01 de

Então, por favor me diga como recuperar os serviços que preciso nas propriedades anexadas. Eles são sempre estáticos, tanto o DependencyPropertycampo de apoio quanto seus métodos Get e Set.
springy76

12

Eu opto por uma abordagem de "visão primeiro", onde passo o modelo de visão para o construtor da visão (em seu code-behind), que é atribuído ao contexto de dados, por exemplo

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

        DataContext = viewModel;
    }
}

Isso substitui sua abordagem baseada em XAML.

Eu uso a estrutura Prism para lidar com a navegação - quando algum código solicita que uma determinada visualização seja exibida ("navegando" até ela), o Prism resolverá essa visualização (internamente, usando a estrutura DI do aplicativo); a estrutura de DI, por sua vez, resolverá quaisquer dependências que a visualização tenha (o modelo de visualização em meu exemplo), então resolverá suas dependências e assim por diante.

A escolha do framework de DI é irrelevante, pois todos eles fazem essencialmente a mesma coisa, ou seja, você registra uma interface (ou um tipo) junto com o tipo concreto que deseja que o framework instancie quando encontrar uma dependência dessa interface. Só para constar, uso o Castle Windsor.

A navegação do prisma leva algum tempo para se acostumar, mas é muito boa uma vez que você se familiariza com ela, permitindo compor seu aplicativo usando diferentes visualizações. Por exemplo, você pode criar uma "região" do Prism em sua janela principal e, em seguida, usando a navegação do Prism, você pode alternar de uma visualização para outra dentro desta região, por exemplo, conforme o usuário seleciona itens de menu ou qualquer outra coisa.

Como alternativa, dê uma olhada em uma das estruturas MVVM, como MVVM Light. Não tenho experiência com isso, então não posso comentar sobre como eles devem ser usados.


1
Como você passa argumentos do construtor para visualizações filhas? Eu tentei essa abordagem, mas obtenho exceções na visualização pai me informando que a visualização filho não tem um construtor sem parâmetros padrão
Doutor Jones

10

Instale o MVVM Light.

Parte da instalação é criar um localizador de modelo de vista. Esta é uma classe que expõe seus modelos de visão como propriedades. O getter dessas propriedades pode, então, ser instâncias retornadas de seu mecanismo IOC. Felizmente, o MVVM light também inclui a estrutura SimpleIOC, mas você pode conectar outras, se desejar.

Com IOC simples, você registra uma implementação em um tipo ...

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

Neste exemplo, seu modelo de visão é criado e passado a um objeto de provedor de serviço de acordo com seu construtor.

Em seguida, você cria uma propriedade que retorna uma instância do IOC.

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

A parte inteligente é que o localizador do modelo de visualização é então criado em app.xaml ou equivalente como uma fonte de dados.

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

Agora você pode vincular à propriedade 'MyViewModel' para obter seu modelo de visualização com um serviço injetado.

Espero que ajude. Pedimos desculpas por quaisquer imprecisões de código, codificados da memória em um iPad.


Você não deve ter um GetInstanceou resolvefora do bootstrap do aplicativo. Esse é o objetivo do DI!
Soleil - Mathieu Prévot

Concordo que você pode definir o valor da propriedade durante a inicialização, mas sugerir que usar a instanciação lenta é contra o DI é errado.
kidshaw de

@kishaw eu não fiz.
Soleil - Mathieu Prévot

3

Estojo Canonic DryIoc

Respondendo a um post antigo, mas fazendo isso DryIoce fazendo o que acho um bom uso de DI e interfaces (uso mínimo de classes concretas).

  1. O ponto de partida de um aplicativo WPF é App.xaml, e lá dizemos qual é a visualização inicial a ser usada; fazemos isso com code behind em vez do xaml padrão:
  2. remover StartupUri="MainWindow.xaml"em App.xaml
  3. em codebehind (App.xaml.cs) adicione isto override OnStartup:

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

esse é o ponto de inicialização; esse também é o único lugar onde resolvedeve ser chamado.

  1. a raiz da configuração (de acordo com o livro de Mark Seeman Dependency inject in .NET; o único lugar onde as classes concretas devem ser mencionadas) estará no mesmo código por trás, no construtor:

    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>();
    }

Observações e mais alguns detalhes

  • Usei a classe concreta apenas com a vista MainWindow;
  • Tive que especificar qual construtor usar (precisamos fazer isso com DryIoc) para o ViewModel, porque o construtor padrão precisa existir para o designer XAML, e o construtor com injeção é o real usado para o aplicativo.

O construtor ViewModel com DI:

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

Construtor padrão do ViewModel para design:

public MainWindowViewModel()
{
}

O código por trás da visualização:

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

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

e o que é necessário na visualização (MainWindow.xaml) para obter uma instância de design com ViewModel:

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

Conclusão

Portanto, obtivemos uma implementação muito limpa e mínima de um aplicativo WPF com um contêiner DryIoc e DI, enquanto mantemos as instâncias de design de visualizações e modelos de visualização possíveis.


2

Use a estrutura de extensibilidade gerenciada .

[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);
}

Em geral, o que você faria é ter uma classe estática e usar o Factory Pattern para fornecer um contêiner global (em cache, natch).

Quanto a como injetar os modelos de visualização, você os injeta da mesma forma que injeta todo o resto. Crie um construtor de importação (ou coloque uma instrução de importação em uma propriedade / campo) no code-behind do arquivo XAML e diga a ele para importar o modelo de exibição. Em seguida, ligam o seu Windowé DataContexta essa propriedade. Os objetos raiz que você mesmo retira do contêiner geralmente são Windowobjetos compostos . Basta adicionar interfaces às classes de janela, exportá-las e, em seguida, obter do catálogo como acima (em App.xaml.cs ... esse é o arquivo de inicialização WPF).


Você está perdendo um ponto importante da DI que é evitar qualquer criação de instância com new.
Soleil - Mathieu Prévot

0

Eu sugeriria usar o ViewModel - Primeira abordagem https://github.com/Caliburn-Micro/Caliburn.Micro

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

usar Castle Windsorcomo contêiner IOC.

Tudo sobre convenções

Uma das principais características do Caliburn.Micro se manifesta em sua capacidade de eliminar a necessidade de código padrão, agindo de acordo com uma série de convenções. Algumas pessoas adoram convenções e outras as odeiam. É por isso que as convenções do CM são totalmente personalizáveis ​​e podem até ser desativadas completamente se não for desejado. Se você for usar convenções, e como elas estão ativadas por padrão, é bom saber o que são essas convenções e como funcionam. Esse é o assunto deste artigo. Resolução de visualização (ViewModel-First)

Fundamentos

A primeira convenção que você provavelmente encontrará ao usar CM está relacionada à resolução da visualização. Essa convenção afeta quaisquer áreas ViewModel-First de seu aplicativo. Em ViewModel-First, temos um ViewModel existente que precisamos renderizar na tela. Para fazer isso, CM usa um padrão de nomenclatura simples para encontrar um UserControl1 que deve ser vinculado ao ViewModel e exibi-lo. Então, qual é esse padrão? Vamos apenas dar uma olhada em ViewLocator.LocateForModelType para descobrir:

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);
};

Vamos ignorar a variável de “contexto” em primeiro lugar. Para derivar a visualização, presumimos que você está usando o texto “ViewModel” na nomenclatura de suas VMs, portanto, apenas alteramos para “Visualização” em todos os lugares em que o encontramos, removendo a palavra “Modelo”. Isso tem o efeito de alterar os nomes dos tipos e os namespaces. Portanto, ViewModels.CustomerViewModel se tornaria Views.CustomerView. Ou se você estiver organizando seu aplicativo por recurso: CustomerManagement.CustomerViewModel torna-se CustomerManagement.CustomerView. Esperançosamente, isso é bastante simples. Assim que tivermos o nome, procuramos os tipos com esse nome. Pesquisamos qualquer assembly que você expôs ao CM como pesquisável via AssemblySource.Instance.2 Se encontrarmos o tipo, criamos uma instância (ou obtemos uma do contêiner IoC, se estiver registrado) e a devolvemos ao chamador. Se não encontrarmos o tipo,

Agora, de volta ao valor de “contexto”. É assim que o CM oferece suporte a múltiplas visualizações no mesmo ViewModel. Se um contexto (normalmente uma string ou enum) é fornecido, fazemos uma transformação adicional do nome, com base nesse valor. Essa transformação efetivamente assume que você tem uma pasta (namespace) para as diferentes visualizações, removendo a palavra “Visualização” do final e acrescentando o contexto. Portanto, dado um contexto de “Master”, nosso ViewModels.CustomerViewModel se tornaria Views.Customer.Master.


2
Toda a sua postagem é opinião.
John Peters,

-1

Remova o uri de inicialização do seu 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);
    }
}

Agora você pode usar sua classe IoC para construir as instâncias.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

Você não deve ter nenhum contêiner GetInstancede resolveapp.xaml.cs externo, você está perdendo o ponto de DI. Além disso, mencionar a visão xaml no código-base da visão é meio complicado. Basta chamar a visualização em c # puro e fazer isso com o contêiner.
Soleil - Mathieu Prévot
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.