web-dev-qa-db-fra.com

Qu'est-ce qu'un ViewModelLocator et quels sont ses avantages / inconvénients par rapport aux DataTemplates?

Quelqu'un peut-il me donner un bref résumé de ce qu'est un ViewModelLocator, de son fonctionnement et des avantages/inconvénients de son utilisation par rapport aux DataTemplates?

J'ai essayé de trouver des informations sur Google, mais il semble y avoir de nombreuses implémentations différentes et aucune liste de rayures sur ce que c'est et les avantages/inconvénients de l'utiliser.

109
Rachel

Intro

Dans MVVM, la pratique habituelle est que les vues trouvent leurs ViewModels en les résolvant à partir d'un conteneur injection de dépendance (DI). Cela se produit automatiquement lorsque le conteneur est invité à fournir (résoudre) une instance de la classe View. Le conteneur injecte le ViewModel dans la vue en appelant un constructeur de la vue qui accepte un paramètre ViewModel; ce schéma est appelé inversion de contrôle (IoC).

Avantages de DI

Le principal avantage ici est que le conteneur peut être configuré au moment de l'exécution avec des instructions sur la façon de résoudre les types que nous lui demandons. Cela permet une plus grande testabilité en lui demandant de résoudre les types (vues et modèles d'affichage) que nous utilisons lorsque notre application s'exécute réellement, mais en l'instruisant différemment lors de l'exécution des tests unitaires de l'application. Dans ce dernier cas, l'application n'aura même pas d'interface utilisateur (elle n'est pas en cours d'exécution, seuls les tests le sont), de sorte que le conteneur résoudra mocks à la place des types "normaux" utilisés lors de l'exécution de l'application.

Problèmes liés à DI

Jusqu'à présent, nous avons vu que l'approche DI permet une testabilité facile pour l'application en ajoutant une couche d'abstraction sur la création de composants d'application. Il y a un problème avec cette approche: elle ne fonctionne pas bien avec les concepteurs visuels tels que Microsoft Expression Blend.

Le problème est que dans les exécutions normales des applications et les tests unitaires, quelqu'un doit configurer le conteneur avec des instructions sur les types à résoudre; en outre, quelqu'un doit demander le conteneur pour résoudre les vues afin que les ViewModels puissent y être injectés.

Cependant, au moment du design, il n'y a pas de code en cours d'exécution. Le concepteur tente d'utiliser la réflexion pour créer des instances de nos vues, ce qui signifie que:

  • Si le constructeur de View nécessite une instance de ViewModel, le concepteur ne pourra pas du tout instancier la View - il se produira une erreur de manière contrôlée
  • Si la vue a un constructeur sans paramètre, la vue sera instanciée, mais son DataContext sera null donc nous aurons une vue "vide" dans le concepteur - ce qui n'est pas très utile

Entrez ViewModelLocator

Le ViewModelLocator est une abstraction supplémentaire utilisée comme ceci:

  • La vue elle-même instancie un ViewModelLocator dans le cadre de son ressources et lie son DataContext à la propriété ViewModel du localisateur
  • Le localisateur en quelque sorte détecte si nous sommes en mode conception
  • S'il n'est pas en mode conception, le localisateur renvoie un ViewModel qu'il résout à partir du conteneur DI, comme expliqué ci-dessus
  • Si en mode conception, le localisateur renvoie un ViewModel "factice" fixe utilisant sa propre logique (rappelez-vous: il n'y a pas de conteneur au moment de la conception!); ce ViewModel est généralement pré-rempli avec des données factices

Bien sûr, cela signifie que la vue doit avoir un constructeur sans paramètre pour commencer (sinon le concepteur ne pourra pas l'instancier).

Sommaire

ViewModelLocator est un idiome qui vous permet de conserver les avantages de DI dans votre application MVVM tout en permettant à votre code de bien jouer avec les concepteurs visuels. C'est ce qu'on appelle parfois la "possibilité de fusion" de votre application (en se référant à Expression Blend).

Après avoir digéré ce qui précède, voir un exemple pratique ici .

Enfin, l'utilisation de modèles de données n'est pas une alternative à l'utilisation de ViewModelLocator, mais une alternative à l'utilisation de paires View/ViewModel explicites pour des parties de votre interface utilisateur. Vous constaterez souvent qu'il n'est pas nécessaire de définir une vue pour un ViewModel car vous pouvez utiliser un modèle de données à la place.

190
Jon

Un exemple d'implémentation de @ Jon's answer

J'ai une classe de localisateur de modèle de vue. Chaque propriété va être une instance du modèle de vue que je vais allouer sur ma vue. Je peux vérifier si le code s'exécute en mode conception ou n'utilise pas DesignerProperties.GetIsInDesignMode. Cela me permet d'utiliser un modèle fictif lors de la conception du temps et de l'objet réel lorsque j'exécute l'application.

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

Et pour l'utiliser, je peux ajouter mon localisateur à App.xaml Ressources:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

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

Et puis pour câbler votre vue (ex: MainView.xaml) à votre modèle de vue:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
8
BrunoLM

Je ne comprends pas pourquoi les autres réponses à cette question tournent autour du concepteur.

Le but du View Model Locator est de permettre à votre vue d'instancier cela (oui, View Model Locator = View First):

public void MyWindowViewModel(IService someService)
{
}

au lieu de cela:

public void MyWindowViewModel()
{
}

en déclarant ceci:

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

ViewModelLocator est une classe, qui fait référence à un IoC et c'est ainsi qu'il résout la propriété MainWindowModel qu'il expose.

Cela n'a rien à voir avec la fourniture de modèles de vue simulée à votre vue. Si vous le voulez, faites-le

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

Le localisateur de modèle de vue est un wrapper autour de tout (n'importe quel) conteneur d'inversion de contrôle, tel que Unity par exemple.

Faire référence à:

2
Hristo Yankov