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

112

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 de celui-ci et aucune liste striaght quant à ce que c'est et les avantages / inconvénients de son utilisation.

Rachel
la source

Réponses:

204

Intro

Dans MVVM, la pratique habituelle consiste à demander aux vues de trouver leurs ViewModels en les résolvant à partir d'un conteneur d' injection de dépendances (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 (Views et ViewModels) 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 pour 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), donc le conteneur résoudra les simulations à la place des types "normaux" utilisés lorsque l'application s'exécute.

Problèmes découlant de l'ID

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 d'applications normales et les exécutions de tests unitaires, quelqu'un doit configurer le conteneur avec des instructions sur les types à résoudre; De plus, quelqu'un doit demander au conteneur de résoudre les vues afin que les ViewModels puissent y être injectés.

Cependant, au moment de la conception, aucun de nos codes n'est 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 vue nécessite une instance de ViewModel, le concepteur ne pourra pas du tout instancier la vue - il provoquera une erreur de manière contrôlée
  • Si la vue a un constructeur sans paramètre, la vue sera instanciée, mais elle le DataContextsera nullainsi nous obtiendrons 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 ses ressources et lie son DataContext à la propriété ViewModel du localisateur
  • Le localisateur détecte en quelque sorte 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 en utilisant sa propre logique (rappelez-vous: il n'y a pas de conteneur au moment du design!); 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).

Résumé

ViewModelLocator est un idiome qui vous permet de conserver les avantages de la DI dans votre application MVVM tout en permettant à votre code de bien jouer avec les concepteurs visuels. Ceci est parfois appelé la «possibilité de fusion» de votre application (en référence à Expression Blend).

Après avoir digéré ce qui précède, voyez 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 certaines 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.

Jon
la source
4
+1 pour une excellente explication. Pouvez-vous développer davantage la vue et ses ressources? Par ressources, voulez-vous dire les propriétés de la vue? Ou? Avez-vous un lien avec un exemple concret de ce modèle?
Metro Schtroumpf
@MetroSmurf: Votre lien se trouve dans la section Résumé.
Jon
1
Merci. Y a-t-il des limites à l'utilisation d'un ViewModelLocator? J'avais quelques inquiétudes quant au fait qu'il faisait référence à une ressource statique - les ViewModels peuvent-ils être créés dynamiquement au moment de l'exécution? Et y a-t-il beaucoup de code supplémentaire impliqué pour en connecter un?
Rachel
@Rachel: Ce même lien dans le Résumé devrait répondre à ces questions avec des exemples pratiques.
Jon
2
Réponse extrêmement trompeuse. L'objectif principal de View Model Locator n'est pas de fournir des données factices au concepteur. Vous pouvez facilement le faire en spécifiant d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}". Le but du localisateur est en fait d'activer DI sur les vues, car WPF est si mauvais pour le fournir. Exemple: vous avez une fenêtre principale qui ouvre une fenêtre de dialogue. Pour résoudre le DI sur la fenêtre de dialogue de la manière habituelle, vous devez le passer comme une dépendance de la fenêtre principale! Ceci est évité avec le localisateur de vue.
hyankov
10

Un exemple d'implémentation de la réponse de @ Jon

J'ai une classe de localisateur de modèle de vue. Chaque propriété sera une instance du modèle de vue que je vais allouer à ma vue. Je peux vérifier si le code s'exécute en mode conception ou non DesignerProperties.GetIsInDesignMode. Cela me permet d'utiliser un modèle simulé pendant la conception et 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 aux App.xamlressources:

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 viewmodel:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
BrunoLM
la source
y a-t-il une différence à utiliser thisau lieu de dummy?
Sebastian Xawery Wiśniowiecki le
5

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

Le but du View Model Locator est de permettre à votre View de l'instancier (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}}"

ViewModelLocatorest la classe, qui fait référence à un IoC et c'est ainsi qu'elle résout la MainWindowModelpropriété qu'elle expose.

Cela n'a rien à voir avec la fourniture de modèles de vue simulée à votre vue. Si tu veux ça, fais juste

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

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

Faire référence à:

Hyankov
la source
Un localisateur de modèle de vue ne nécessite pas de conteneur, l'utilisateur décide de la façon dont le modèle de vue est résolu via la configuration et vous pouvez utiliser un conteneur ou simplement créer un nouveau type vous-même. Ainsi, vous pouvez faire un emplacement de modèle de vue basé sur des conventions, par exemple au lieu de pré-enregistrer toutes vos vues et modèles de vue dans un conteneur.
Chris Bordeman
Vous avez raison de dire " Je ne comprends pas pourquoi les autres réponses [...] tournent autour du concepteur " +1, mais le but du localisateur est de supprimer de la vue toute connaissance de la façon dont le modèle de vue est créé, rendant la vue indépendante de cette instanciation, laissant cela au localisateur. Le localisateur sera en mesure de fournir différentes saveurs du modèle de vue, peut-être des versions personnalisées ajoutées via des plugins que le localisateur gérera (et une spécifique pour la conception). La vue sera propre de tout ce processus de localisation de la version correcte du modèle de vue, c'est en effet bon pour SoC.
min