L'Injection de dépendances est-elle possible avec une application WPF?

je veux commencer à utiliser l'injection de dépendances dans mon application WPF, en grande partie pour une meilleure testabilité de l'unité. Mon application est principalement construite sur le modèle M-V-VM. Je suis à la recherche d' autofac pour mon container CIO, mais je ne pense pas que cela importe trop pour cette discussion.

injecter un service dans la fenêtre de démarrage semble simple, car je peux créer le conteneur et résoudre à partir de celui-ci dans App.XAML.cs.

ce qui me pose problème, C'est comment je peux DI Les modèles de vue et les Services dans les contrôles D'utilisateur? Les contrôles d'utilisateur sont instanciés via le markup XAML, donc il n'y a pas d'opportunité de Resolve().

le mieux que je puisse imaginer est de placer le container dans un Singleton, et d'avoir les contrôles de l'utilisateur résoudre leurs modèles de vue à partir du container global. Cela ressemble à une solution à mi-chemin, au mieux, car il fallait encore que mes composants soient dépendants d'un Localisateur de service.

le COI intégral est-il possible avec le WPF?

[modifier] - Prism a été suggéré, mais même évaluer Prism semble être un gros investissement, j'espère quelque chose de plus petit

<!-Voici un fragment de code où je suis arrêté

    //setup IoC container (in app.xaml.cs)
    var builder = new ContainerBuilder();
    builder.Register<NewsSource>().As<INewsSource>();
    builder.Register<AViewModel>().FactoryScoped();
    var container = builder.Build();

    // in user control ctor -
    // this doesn't work, where do I get the container from
    VM = container.Resolve<AViewModel>();

    // in app.xaml.cs
    // this compiles, but I can't use this uc, 
    //as the one I want in created via xaml in the primary window
    SomeUserControl uc = new SomeUserControl();
    uc.VM = container.Resolve<AViewModel>();
34
demandé sur Cœur 2008-11-12 21:44:56

9 réponses

C'est en fait très facile à faire. Nous en avons des exemples dans Prism comme jedidja mentionné. Vous pouvez soit faire injecter ViewModel avec la vue, soit faire injecter ViewModel à la vue. Dans le Prism StockTraderRI vous verrez que nous injectons la vue dans le modèle de vue. Essentiellement ce qui se passe est que la vue (et l'interface de vue) a une propriété de modèle. Cette propriété est implémentée dans le codebehind pour définir le DataContext à la valeur, par exemple:this.DataContext = value;. Dans le constructeur du modèle de vue, la vue est injectée. Il définit ensuite View.Model = this; qui va se passer comme le Datacontexte.

vous pouvez aussi facilement faire l'inverse et faire injecter le modèle de vue dans la vue. Je préfère cela, car cela signifie que ce Dernier n'a plus aucune référence à la vue à tous. Cela signifie que lorsque l'unité-tester le modèle de vue, vous n'avez même pas une vue de se moquer. En outre, il rend le code plus propre, en ce que dans le constructeur de la vue, il Datecontexte du modèle de vue qui a été injecté.

j'en parle un peu plus dans l'enregistrement vidéo de la discussion sur les Modèles de présentation séparés que Jeremy Miller et moi avons donnée à Kaizenconf. La première partie peut être trouvée ici http://www.vimeo.com/2189854.

J'espère que cela aidera, Glenn

16
répondu Glenn Block 2011-02-12 17:57:29

je pense que vous avez touché à la question. Les contrôles doivent être injectés dans leur parent plutôt que créés de manière déclarative par le biais de XAML.

pour que DI fonctionne, un conteneur DI doit créer la classe qui accepte les dépendances. Cela signifie que le parent n'aura aucune instance des commandes de l'enfant au moment de la conception et ressemblera à une coquille dans le concepteur. C'est probablement l'approche recommandée.

L'autre "alternative" est d'avoir un global static container appelé du constructeur du contrôle, ou quelque chose de similaire. Il existe un modèle commun dans lequel deux constructeurs sont déclarés, l'un avec une liste de paramètres pour l'injection du constructeur et l'autre sans paramètres qui délègue:

// For WPF
public Foo() : this(Global.Container.Resolve<IBar>()) {}

// For the rest of the world
public Foo(IBar bar) { .. }

j'appellerais presque cela un antimodèle mais pour le fait que certains cadres ne laissent pas d'autre choix.

Je ne suis même pas un demi-expert en WPF donc je m'attends à un bon service de downmod ici :) mais l'espoir cela aide. Le groupe Autofac (lié à la page d'accueil) pourrait être un autre endroit pour poser cette question. Les exemples D'applications Prism ou MEF (qui comprennent quelques exemples WPF) devraient vous donner une idée de ce qui est possible.

8
répondu user30493 2008-11-22 17:43:30

vous devriez jeter un coup d'oeil à Prism de l'équipe p&p. Voici le site sur Codeplex

5
répondu Jedidja 2008-11-12 18:46:25

Vous devriez jeter un oeil à Caliburn - c'est un simple cadre WPF/Silverlight MVC avec support pour Full DI. Il a l'air vraiment cool et il vous permet d'utiliser n'importe quel conteneur CIO que vous voulez. Il y a quelques exemples sur le wiki de documentation

3
répondu mookid8000 2008-12-03 12:06:06

Humm, Nous sommes confrontés à un problème similaire, nous attendons avec impatience une solution qui fournira un support de temps de conception sous Expression Blend 2.0 (Type fort). De plus, nous attendons avec impatience une solution pour avoir un échantillon de données simulé+généré automatiquement sous Expression Blend.

bien sûr, nous cherchons aussi à avoir tout cela fonctionne en utilisant un modèle de CIO.

Paul Stovell comme article intéressant pour commencer: http://www.paulstovell.com/blog/wpf-dependency-injection-in-xaml

donc j'essaie une chose ou deux pour ajouter un support de temps de conception plus utile pour lier et moquer l'objet au moment de la conception, en ce moment j'ai la plupart de mon problème lié à obtenir une forte connexion dactylographiée faite entre la vue (code) et le ModelView(Xaml), j'ai essayé un scénario de couple:

1.) Solution 1: utiliser Generic pour créer la vue

public class MyDotNetcomponent&lt;T&gt; : SomeDotNetcomponent
{
    // Inversion of Control Loader…
    // Next step add the Inversion of control manager plus
    // some MockObject feature to work under design time
    public T View {Get;}
}

Cette solution ne permet pas travailler car Blend ne supporte pas la surface de design de Generic inside is, mais Xaml en a, bien travailler à l'exécution mais pas à la conception;

2.) Solution 2: ObjectDataProvider

<ObjectDataProvider ObjectType="{x:Type CP:IFooView}" />
<!-- Work in Blend -->
<!—- IOC Issue: we need to use a concrete type and/or static Method there no way to achive a load on demande feature in a easy way -->

3.) Solution 3: Hériter ObjectDataProvider

<CWD:ServiceObjectDataProvider ObjectType="{x:Type CP:IFooView}" />
<!-- Cannot inherit from ObjectDataProvider to achive the right behavior everything is private-->

4.) Solution 4: Créer un objet mockdataprovider à partir de zéro jusqu'à la tâche

<CWD:ServiceObjectDataProvider ObjectType="{x:Type CP:IFooView }" />
<!-- Not working in Blend, quite obvious-->

5. Solution 5: Créer une Extension de Balisage (Paul Stovell)

<CWM:ServiceMarkup MetaView="{x:Type CP:IFooView}"/>
<!-- Not working in Blend -->

juste pour clarifier un point quand j'ai dit "ne pas travailler en mélange", je veux dire que la boîte de dialogue de liaison n'est pas utilisable et que le concepteur doit réécrire lui-même le XAML.

Notre prochaine étape sera probablement de prendre le temps d'évaluer la possibilité de créer un plug-in pour l'Expression Blend.

3
répondu Jordan S. Jones 2013-03-07 18:57:31

oui, on le fait tout le temps. Vous pouvez "injecter" votre ViewModel dans le DataContext du contrôle.

en fait, je trouve que WPF est encore plus facile à utiliser avec DI. Même les objets de dépendance et les propriétés fonctionnent de manière transparente.

2
répondu Justin Bozonier 2008-11-12 19:03:44

Glen Block (voir ci-dessus) mentionne qu'une approche commune est de concevoir votre MVVM la solution pour utiliser le DataContext comme l'endroit où vous pouvez "résoudre" votre modèle de vue dans la vue. Ensuite, vous pouvez utiliser les extensions de design d'expression blend 2008 (notez que vous n'avez pas besoin d'utiliser les outils de design d'expression blend pour en profiter). Par exemple:

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:MyViewModelMock, IsDesignTimeCreatable=True}"

à votre avis, vous pouvez avoir un getter de propriété qui jette votre DataContext au type que vous attendez (juste pour le rendre plus facile à consommer dans le code-derrière).

private IMyViewModel ViewModel { get { return (IMyViewModel) DataContext; } }

N'oubliez pas d'utiliser un interface pour que vos vues soient plus faciles à tester, ou pour vous aider à injecter différentes implémentations d'exécution.

en général, vous ne devriez pas résoudre des choses à partir du conteneur partout dans votre solution. Il est en fait considéré comme une mauvaise pratique de passer votre conteneur dans chaque constructeur, ou pour le rendre accessible à l'échelle mondiale. (Vous devriez consulter les discussions sur la raison pour laquelle les stratégies" Localisateur de Service "constituent un"anti-modèle").

créer un constructeur de vue publique avec des dépendances explicites que le conteneur (par exemple Prism Unity ou MEF) peut résoudre.

Si nécessaire, vous pouvez également créer un interne constructeur par défaut pour créer une maquette de votre modèle de vue (ou réel). Cela protège contre l'utilisation accidentelle de ce "constructeur" extérieurement (dans votre "coquille" ou ailleurs). Vos projets de test peuvent également utiliser de tels constructeurs en utilisant le "interne Visibletoattribute" par " AssemblyInfo". Mais bien sûr, ce n'est généralement pas nécessaire puisque vous pouvez injecter vos moqueries en utilisant les constructeurs de dépendances complets de toute façon, et parce que la majorité de vos tests devraient se concentrer sur le ViewModel en premier lieu. N'importe quel code dans le View devrait idéalement être tout à fait banale. (Si votre Vue nécessite beaucoup de tests, alors vous pourriez vous demander pourquoi!)

Glen mentionne aussi que vous pouvez injecter Vues dans des Modèles de Vue, ou voir les Modèles dans les Vues. Je préfère de loin cette dernière, car il existe de très bonnes techniques pour tout découpler (utilisation de la liaison déclarative, du commandement, de L'agrégation D'événements, des modèles de médiateurs, etc.).). Modèle De Vue est l'endroit où tous les le levage lourd sera fait pour orchestrer la logique d'affaires de base. Si tous les points "contraignants" nécessaires sont fournis par Modèle De Vue, il ne devrais vraiment pas besoin de le savoir à propos de la View (qui peut généralement être câblé à lui déclarativement dans le XAML).

si nous rendons le modèle de vue agnostique à la source de l'interaction utilisateur, cela le rend beaucoup plus facile à tester (de préférence en premier). Et cela signifie aussi que vous pouvez facilement brancher vue (WPF, Silverlight, ASP.NET, Console, etc.). En fait, pour s'assurer qu'un découplage approprié a été réalisé, nous pouvons nous demander si une architecture "MVM" (Model-ViewModel) pourrait fonctionner dans le contexte, par exemple, d'un service de flux de travail. Lorsque vous arrêtez d'y penser, la plupart de vos tests unitaires seront probablement conçus sur cette prémisse.

1
répondu Ben Stabile 2013-10-29 16:37:29

je pense que Vous avez à Décider d'Abord la Vue ou Viewmodel en Premier, alors que compte tenu de la réponse, il Peut être de décider.. Il existe plusieurs open source framework fait la même chose . J'utilise Caliburn où ViewModel est pris en premier et son approche vraiment bonne

0
répondu satish 2011-03-14 08:43:26

j'ai écrit un framework très léger où un ViewModel est résolu à l'exécution en utilisant un IoC (Unité) comme extension de markup.

le framework permet D'écrire XAML sans code derrière mais vous permet tout de même d'avoir des commandes routées, des liaisons de données et des gestionnaires d'événements.

En tout cas, je ne pense pas que vous avez besoin de la XAML libre dans votre cas, mais si vous regardez le code (http://xtrememvvm.codeplex.com), il pourrait s'avérer que vous pouvez utiliser le code de résolvez vos propres problèmes avec les modèles de vue d'injection et les Services.

0
répondu Clay Ver Valen 2013-03-07 17:45:12