O que é ViewModelLocator e quais são seus prós / contras em comparação com DataTemplates?

112

Alguém pode me dar um rápido resumo do que é um ViewModelLocator, como funciona e quais são os prós / contras de usá-lo em comparação com DataTemplates?

Eu tentei encontrar informações no Google, mas parece haver muitas implementações diferentes dele e nenhuma lista forte sobre o que é e os prós / contras de usá-lo.

Rachel
fonte

Respostas:

204

Introdução

No MVVM, a prática usual é fazer com que as Views encontrem seus ViewModels resolvendo-os a partir de um contêiner de injeção de dependência (DI). Isso acontece automaticamente quando o contêiner é solicitado a fornecer (resolver) uma instância da classe View. O contêiner injeta ViewModel em View chamando um construtor de View que aceita um parâmetro ViewModel; este esquema é denominado inversão de controle (IoC).

Benefícios do DI

O principal benefício aqui é que o contêiner pode ser configurado em tempo de execução com instruções sobre como resolver os tipos que solicitamos dele. Isso permite maior testabilidade, instruindo-o a resolver os tipos (Views e ViewModels) que usamos quando nosso aplicativo realmente é executado, mas instruindo-o de forma diferente ao executar os testes de unidade para o aplicativo. No último caso, o aplicativo nem mesmo terá uma IU (não está em execução; apenas os testes estão), portanto, o contêiner resolverá simulações no lugar dos tipos "normais" usados ​​quando o aplicativo é executado.

Problemas decorrentes de DI

Até agora, vimos que a abordagem de DI permite fácil testabilidade para o aplicativo, adicionando uma camada de abstração sobre a criação de componentes do aplicativo. Há um problema com essa abordagem: ela não funciona bem com designers visuais como o Microsoft Expression Blend.

O problema é que, tanto na execução normal do aplicativo quanto na execução do teste de unidade, alguém precisa configurar o contêiner com instruções sobre quais tipos resolver; além disso, alguém tem que pedir ao contêiner para resolver as visualizações para que os ViewModels possam ser injetados nelas.

No entanto, em tempo de design não há nenhum código nosso rodando . O designer tenta usar reflexão para criar instâncias de nossas visualizações, o que significa que:

  • Se o construtor de View requer uma instância de ViewModel, o designer não será capaz de instanciar a View - ocorrerá um erro de alguma maneira controlada
  • Se a View tem um construtor sem parâmetros, a View será instanciada, mas assim DataContextserá null, teremos uma view "vazia" no designer - o que não é muito útil

Insira ViewModelLocator

O ViewModelLocator é uma abstração adicional usada assim:

  • O próprio View instancia um ViewModelLocator como parte de seus recursos e vincula seu DataContext à propriedade ViewModel do localizador
  • O localizador de alguma forma detecta se estamos no modo de design
  • Se não estiver no modo de design, o localizador retorna um ViewModel que ele resolve do contêiner DI, conforme explicado acima
  • Se estiver no modo de design, o localizador retorna um ViewModel "fictício" fixo usando sua própria lógica (lembre-se: não há contêiner em tempo de design!); este ViewModel normalmente vem pré-preenchido com dados fictícios

Claro, isso significa que a View deve ter um construtor sem parâmetros para começar (caso contrário, o designer não será capaz de instanciá-lo).

Resumo

ViewModelLocator é um idioma que permite manter os benefícios do DI em seu aplicativo MVVM enquanto também permite que seu código funcione bem com designers visuais. Isso às vezes é chamado de "capacidade de mesclagem" de seu aplicativo (referindo-se ao Expression Blend).

Depois de digerir o acima, veja um exemplo prático aqui .

Finalmente, usar modelos de dados não é uma alternativa ao uso de ViewModelLocator, mas uma alternativa ao uso de pares View / ViewModel explícitos para partes de sua IU. Freqüentemente, você pode descobrir que não há necessidade de definir um View para um ViewModel porque você pode usar um modelo de dados em seu lugar.

Jon
fonte
4
1 para uma ótima explicação. Você pode expandir ainda mais a visão e seus recursos? Por Recursos, você quer dizer as propriedades da Visualização? Ou? Você tem um link com um exemplo concreto para esse padrão?
Metro Smurf de
@MetroSmurf: Seu link está na seção Resumo.
Jon
1
Obrigado. Há alguma limitação no uso de ViewModelLocator? Tive algumas preocupações sobre o fato de fazer referência a um recurso estático - ViewModels podem ser criados dinamicamente em tempo de execução? E há muito código extra envolvido na conexão de um?
Rachel
@Rachel: O mesmo link no Resumo deve responder a essas perguntas com exemplos práticos.
Jon
2
Resposta extremamente enganosa. O objetivo principal do View Model Locator não é fornecer dados fictícios ao designer. Você pode fazer isso facilmente especificando d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}". O propósito do Locator é realmente habilitar DI nas Views, porque WPF é muito ruim em fornecê-lo. Exemplo: você tem uma janela principal que abre uma janela de diálogo. Para resolver a DI na janela de diálogo da maneira usual, você precisa passá-la como uma dependência da janela principal! Isso é evitado com o View Locator.
hyankov
10

Um exemplo de implementação da resposta de @Jon

Eu tenho uma classe de localizador de modelo de exibição. Cada propriedade será uma instância do modelo de visão que alocarei em minha visão. Posso verificar se o código está rodando em modo de design ou não usando DesignerProperties.GetIsInDesignMode. Isso me permite usar um modelo de simulação durante o tempo de design e o objeto real quando estou executando o aplicativo.

public class ViewModelLocator
{
    private DependencyObject dummy = new DependencyObject();

    public IMainViewModel MainViewModel
    {
        get
        {
            if (IsInDesignMode())
            {
                return new MockMainViewModel();
            }

            return MyIoC.Container.GetExportedValue<IMainViewModel>();
        }
    }

    // returns true if editing .xaml file in VS for example
    private bool IsInDesignMode()
    {
        return DesignerProperties.GetIsInDesignMode(dummy);
    }
}

E para usá-lo, posso adicionar meu localizador aos App.xamlrecursos:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

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

E, em seguida, para conectar sua visualização (ex: MainView.xaml) ao seu modelo de visualização:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
BrunoLM
fonte
há alguma diferença em usar em thisvez de dummy?
Sebastian Xawery Wiśniowiecki
5

Não entendo por que as outras respostas desta pergunta envolvem o Designer.

O objetivo do View Model Locator é permitir que seu View instancie isso (sim, View Model Locator = View First):

public void MyWindowViewModel(IService someService)
{
}

em vez de apenas isso:

public void MyWindowViewModel()
{
}

declarando isto:

DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

Onde ViewModelLocatorestá a classe, que faz referência a um IoC e é assim que resolve a MainWindowModelpropriedade que expõe.

Não tem nada a ver com fornecer modelos de visualização Mock para sua visualização. Se você quiser isso, basta fazer

d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"

O View Model Locator é um invólucro em torno de algum (qualquer) container Inversion of Control, como o Unity, por exemplo.

Referir-se:

Hyankov
fonte
Um localizador de modelo de visualização não requer um contêiner, o usuário decide como o modelo de visualização é resolvido por meio da configuração e você pode usar um contêiner ou apenas criar um tipo por conta própria. Assim, você pode fazer a localização do modelo de visualização baseada em convenção, por exemplo, em vez de pré-registrar todas as suas visualizações e modelos de visualização em algum contêiner.
Chris Bordeman de
Você está certo ao dizer " Eu não entendo porque as outras respostas [...] envolvem o Designer " +1, mas o objetivo do localizador é remover da visão qualquer conhecimento de como o modelo de visão é ser criado, tornando a visão independente desta instanciação, deixando isso para o localizador. O localizador será capaz de fornecer diferentes sabores do modelo de visualização, talvez alguns personalizados adicionados por meio de plug-ins que o localizador gerenciará (e um específico para o tempo de design). A visualização não terá nenhum processo de localização da versão correta do modelo de visualização, o que é bom para o SoC.
minutos