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 IStorage
como 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 IStorage
interface, 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 SomeViewModel
do 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.
fonte
Respostas:
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
IStorage
interface como parâmetro do construtor:Crie um
ViewModelLocator
com uma propriedade get para o modelo de visualização, que carrega o modelo de visualização do Ninject:Faça
ViewModelLocator
um recurso para todo o aplicativo em App.xaml:Vincule o
DataContext
deUserControl
à propriedade correspondente no ViewModelLocator.Crie uma classe herdando NinjectModule, que irá configurar as ligações necessárias (
IStorage
e o modelo de visualização):Inicialize o kernel IoC na inicialização do aplicativo com os módulos Ninject necessários (o que está acima por enquanto):
Usei uma
IocKernel
classe estática para manter a instância ampla do aplicativo do kernel IoC, para que possa acessá-la facilmente quando necessário:Essa solução faz uso de um estático
ServiceLocator
(oIocKernel
), 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
fonte
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.Em sua pergunta, você definiu o valor da
DataContext
propriedade 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
DataContext
propriedade 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
StartupUri
propriedade doApp.xaml
arquivo):Isso se baseia em um gráfico de objeto de modelos de visão enraizados no,
RootViewModel
mas 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 deSomeViewModel
do meucs
código, como devo fazer isso?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
DataContext
da 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
DataContext
em 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: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:
Fazendo isso, você pode usar injeção de dependência e manter um bom suporte em tempo de design.
fonte
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 é chamadaIocContainer
. Por quê? Como mencionado, esses são anti-padrões.Disponibilizando em
StandardKernel
qualquer lugarA chave para a magia do Ninject é a
StandardKernel
-Instância necessária para usar o.Get<T>()
-Método.Como alternativa ao sondergard,
IocContainer
você pode criar oStandardKernel
dentro daApp
-Class.Apenas remova StartUpUri do seu App.xaml
Este é o CodeBehind do aplicativo dentro de App.xaml.cs
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
Claro que você também pode injetar um
IViewModel
se 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.Se você precisar de uma instância local do Kernel, pode injetá-la como Propriedade.
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 forma como a Ninject.Extensions.Factory deve ser usada também pode ser exibida em vermelho aqui .
fonte
Ninject.Extensions.Factory
nisso, diga aqui nos comentários e eu adicionarei mais algumas informações.DependencyProperty
campo de apoio quanto seus métodos Get e Set.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
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.
fonte
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 ...
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.
A parte inteligente é que o localizador do modelo de visualização é então criado em app.xaml ou equivalente como uma fonte de dados.
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.
fonte
GetInstance
ouresolve
fora do bootstrap do aplicativo. Esse é o objetivo do DI!Estojo Canonic DryIoc
Respondendo a um post antigo, mas fazendo isso
DryIoc
e fazendo o que acho um bom uso de DI e interfaces (uso mínimo de classes concretas).App.xaml
, e lá dizemos qual é a visualização inicial a ser usada; fazemos isso com code behind em vez do xaml padrão:StartupUri="MainWindow.xaml"
em App.xamlem codebehind (App.xaml.cs) adicione isto
override OnStartup
:esse é o ponto de inicialização; esse também é o único lugar onde
resolve
deve ser chamado.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:
Observações e mais alguns detalhes
MainWindow
;O construtor ViewModel com DI:
Construtor padrão do ViewModel para design:
O código por trás da visualização:
e o que é necessário na visualização (MainWindow.xaml) para obter uma instância de design com ViewModel:
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.
fonte
Use a estrutura de extensibilidade gerenciada .
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
éDataContext
a essa propriedade. Os objetos raiz que você mesmo retira do contêiner geralmente sãoWindow
objetos 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).fonte
new
.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 Windsor
como 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:
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.
fonte
Remova o uri de inicialização do seu app.xaml.
App.xaml.cs
Agora você pode usar sua classe IoC para construir as instâncias.
MainWindowView.xaml.cs
fonte
GetInstance
deresolve
app.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.