Comment gérer l'injection de dépendance dans un WPF/MVVM application
Je suis en train de créer une nouvelle application de bureau et je tiens à le construire en utilisant MVVM et WPF.
J'ai également l'intention d'utiliser TDD.
Le problème est que je ne sais pas comment je dois utiliser un conteneur IoC à injecter mon dépendances sur mon code de production.
Supposons que j'ai la suivantes de la classe et de l'interface:
public interface IStorage
{
bool SaveFile(string content);
}
public class Storage : IStorage
{
public bool SaveFile(string content){
//Saves the file using StreamWriter
}
}
Et puis j'ai une autre classe qui a IStorage
comme une dépendance, supposons aussi que cette classe est un ViewModel ou d'une classe affaires...
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
Avec ce que je peux facilement écrire des tests unitaires pour s'assurer qu'ils fonctionnent correctement, en utilisant les objets fantaisie et etc.
Le problème est lorsqu'il s'agit de l'utiliser dans l'application réelle. Je sais que je dois avoir un conteneur IoC, qui lie une implémentation par défaut pour le IStorage
interface, mais comment puis-je faire?
Par exemple, comment en serait-il si j'ai eu le code xaml suivant:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
Comment puis-je correctement "raconter" WPF pour injecter des dépendances dans ce cas?
Aussi, supposons que j'ai besoin d'une instance de SomeViewModel
de mon cs
code, comment dois je faire?
Je me sens Im complètement perdu dans cette, je vous serais reconnaissant de tout exemple ou des conseils de comment est la meilleure façon de le gérer.
Je suis familier avec StructureMap, mais je ne suis pas un expert. Aussi, si il y a un mieux/plus facile/dehors-de-le-boîte-cadre, s'il vous plaît laissez-moi savoir.
Merci d'avance.
- Avec .net core 3.0 dans l'aperçu, vous pouvez le faire avec Microsoft certains packages nuget.
Vous devez vous connecter pour publier un commentaire.
J'ai été en utilisant Ninject, et trouve que c'est un plaisir de travailler avec. Tout est mis en place dans le code, la syntaxe est assez simple, et il a une bonne documentation (et beaucoup de réponses sur DONC).
Donc, fondamentalement, il va comme ceci:
Créer le modèle de vue, et prendre le IStorage interface en tant que paramètre du constructeur:
Créer un ViewModelLocator avec une propriété pour le modèle de vue, qui charge le modèle de vue de Ninject:
Faire le ViewModelLocator une demande de ressources à l'échelle de l'App.xaml:
Lier le DataContext de la UserControl à la propriété correspondante dans le ViewModelLocator.
Créer une classe héritant NinjectModule, qui sera mis en place de liaisons nécessaires (IStorage et le viewmodel):
Initialiser le Cio noyau de démarrage de l'application avec le nécessaire Ninject modules (celle ci-dessus pour l'instant):
J'ai utilisé une statique IocKernel classe afin de conserver l'application à l'échelle de l'instance du Cio noyau, donc je peux facilement y accéder en cas de besoin:
Cette solution utilise un statique ServiceLocator (le IocKernel), qui est généralement considéré comme un anti-modèle, car elle masque la classe de dépendances. Cependant, il est très difficile d'éviter une sorte de manuel de service de recherche pour les classes de l'INTERFACE utilisateur, car ils doivent avoir un constructeur sans paramètre, et vous ne pouvez pas contrôler l'instanciation de toute façon, de sorte que vous ne peut pas injecter de la VM. Au moins de cette façon vous permet de tester la machine virtuelle dans l'isolement, qui est l'endroit où toute la logique métier est.
Si quelqu'un a une meilleure façon, merci de le faire partager.
EDIT:
La chance Likey a fourni une réponse à se débarrasser de la statique service locator, en laissant Ninject instancier des classes de l'INTERFACE utilisateur. Les détails de la réponse peut être vu ici
DataContext="{Binding [...]}"
. C'est la cause du VS-Designer pour exécuter tous les programmes-Code dans le Constructeur du ViewModel. Dans mon cas, la Fenêtre est être exécuté et modal des blocs de toute interaction afin de VS. On devrait peut-être modifier le ViewModelLocator pas à localiser le "réel" Viewmodel au Moment de la Conception. - Une autre Solution consiste à "Désactiver le Code de Projet", qui permettra également d'éviter tout le reste d'être montré. Vous avez peut-être déjà trouvé une solution élégante à ce. Dans ce cas, j'aimerais s'il vous plaît vous le montrer.:DataContext="{d:DesignInstance vm:UserControlViewModel, IsDesignTimeCreatable=True}"
Il ne l'aide, mais seulement si il n'y a pas sans paramètre constructior sur ViewModel, car alors aucune instance n'est createable de la designer. Je suppose queif (DesignerProperties.GetIsInDesignMode(this) == false)
est la seule possibilité de résoudre ce problème.Dans votre question, vous définissez la valeur de la
DataContext
propriété de la vue dans le XAML. Cela nécessite que votre vue-modèle possède un constructeur par défaut. Cependant, comme vous l'avez remarqué, cela ne fonctionne pas bien avec l'injection de dépendance où vous voulez injecter des dépendances dans le constructeur.Donc vous ne pouvez pas définir la
DataContext
de la propriété dans le code XAML. Au lieu de cela vous avez d'autres alternatives.Si l'application est basée sur une simple vue hiérarchique de modèle, vous pouvez construire la totalité de la vue-modèle de hiérarchie au démarrage de l'application (vous devrez supprimer le
StartupUri
bien de laApp.xaml
fichier):Ceci est basé autour d'un objet graphique de la vue-des modèles à la racine du
RootViewModel
mais vous pouvez également injecter une partie de la vue-modèle usines en vue parent-modèles leur permettant de créer de nouvelles vue d'enfant-les modèles de l'objet graphique n'a pas à être fixé. Cela aussi nous l'espérons répondre à votre question suppose que j'ai besoin d'une instance deSomeViewModel
de moncs
code, comment dois je faire?Si votre demande est plus dynamique dans la nature et peut-être est basé autour de la navigation, vous aurez à crochet dans le code qui effectue la navigation. Chaque fois que vous accédez à un nouveau point de vue vous avez besoin pour créer une vue de modèle (à partir du conteneur d'injection de dépendances), la vue elle-même et de définir les
DataContext
de l'afficher à la vue-modèle. Vous pouvez le faire d'abord la vue de où vous choisissez une vue (modèle basé sur une vue ou vous pouvez le faire vue-modèle premier d'où la vue-modèle détermine vue de leur utilisation. Un MVVM framework fournit cette fonctionnalité de la touche avec une certaine façon pour vous de brancher votre conteneur d'injection de dépendances dans la création de modèles mais vous pouvez également mettre en place vous-même. Je suis un peu vague ici, car en fonction de vos besoins, cette fonctionnalité peut devenir assez complexe. C'est l'une des fonctions de base que vous obtenez à partir d'un framework MVVM mais rouler votre propre dans une application simple vous donnera une bonne compréhension de ce que MVVM cadres de fournir sous le capot.Par ne pas être en mesure de déclarer la
DataContext
dans le code XAML de vous faire perdre au moment de la conception de soutien. Si votre vue-modèle contient quelques données, il apparaît lors de la conception du temps qui peut être très utile. Heureusement, vous pouvez utiliser les attributs de conception également dans WPF. Une façon de le faire est d'ajouter les attributs suivants à l'<Window>
élément ou<UserControl>
en XAML:La vue-modèle type doit avoir deux constructeurs, la valeur par défaut pour les données de conception et un autre pour l'injection de dépendances:
En faisant cela, vous pouvez utiliser l'injection de dépendance et de conserver une bonne conception de la prise en charge au moment.
Ce que je poste ici est une amélioration de sondergard Réponse, parce que ce que je vais dire ne rentre pas dans un Commentaire 🙂
En Fait, je suis l'introduction d'une solution élégante, qui évite la nécessité d'un ServiceLocator et un wrapper pour le
StandardKernel
-Exemple, qui, dans sondergard la Solution est appeléeIocContainer
. Pourquoi? Comme mentionné, ce sont des anti-modèles.Faire la
StandardKernel
disponible partoutLa Clé de Ninject la magie de la
StandardKernel
Instance qui est nécessaire pour utiliser le.Get<T>()
-Méthode.Alternativement à sondergard de
IocContainer
vous pouvez créer leStandardKernel
à l'intérieur de laApp
de Classe.Juste enlever StartUpUri à partir de votre Application.xaml
C'est l'Application du Code à l'intérieur de l'App.xaml.cs
À partir de maintenant, Ninject est vivant et prêt à se battre 🙂
L'injection de votre
DataContext
Comme Ninject est vivant, vous pouvez effectuer toutes sortes d'injections, de l'e.g Propriété Injection par Mutateur ou de l'un des plus communs Constructeur Injection.
C'est comment vous injecter votre ViewModel dans votre
Window
'sDataContext
Bien sûr, vous pouvez également Injecter une
IViewModel
si vous ne les fixations, mais ce n'est pas une partie de cette réponse.Accès au Noyau directement
Si vous avez besoin d'appeler des Méthodes sur le Noyau directement (par exemple,
.Get<T>()
- Méthode),vous pouvez laisser le Noyau injecter lui-même.
Si vous avez besoin d'une instance locale du Noyau que vous pourrait injecter de la Propriété.
Bien que cela peut être très utile, je vous recommande pas de le faire. Il suffit de noter que les objets injectés de cette façon, ne sera pas disponible à l'intérieur du Constructeur, car il est injecté plus tard.
Selon cette lien vous devez utiliser l'usine-Extension au lieu de l'injection de la
IKernel
(DI Conteneur).Comment le Ninject.Extensions.L'usine est à utiliser peut également être rouge ici.
Ninject.Extensions.Factory
sur ce, état ici dans les commentaires et je vais ajouter un peu plus d'informations.DependencyProperty
terrain et de ses méthodes Get et Set.- Je aller pour une "première vue", où je passe de la vue-modèle à l'affichage du constructeur (dans le code-behind), qui est affectée au contexte de données, par exemple
Cela remplace votre XAML approche.
- Je utiliser le Prisme cadre pour gérer la navigation - lorsque certaines demandes de code d'un point de vue particulier être affiché (par "navigation"), l'Prisme permettra de résoudre ce point de vue (interne, à l'aide de l'application de la DI-cadre); le DI-cadre permettra à son tour de résoudre toutes les dépendances que le point de vue (la vue modèle dans mon exemple), puis résout son dépendances, et ainsi de suite.
Choix de DI-cadre est à peu près inutile comme ils le font tous essentiellement la même chose, c'est à dire vous vous inscrivez à une interface (ou un type) ainsi que le type de béton que vous voulez le cadre d'instancier quand il trouve une dépendance de l'interface. Pour l'enregistrement, j'ai utiliser du Château de Windsor.
Prisme de navigation faut s'habituer, mais c'est très bon une fois que vous obtenez votre tête autour de lui, vous permettant de composer votre application à l'aide de différents points de vue. E. g. vous pouvez créer un Prisme de la "région" sur votre fenêtre principale, puis en utilisant le Prisme de navigation vous permettrait de passer d'une vue à l'autre dans cette région, par exemple, que l'utilisateur sélectionne les éléments de menu ou quoi que ce soit.
Vous pouvez prendre un coup d'oeil à l'un des frameworks MVVM comme MVVM Light. J'ai aucune expérience de ces ne peut donc pas commenter sur ce qu'ils aiment utiliser.
Installer MVVM Light.
Partie de l'installation est de créer un modèle de vue de localisation. C'est une classe qui expose votre viewmodel en tant que propriétés. La lecture de ces propriétés peuvent être retournés les instances de votre CIO moteur. Heureusement, MVVM light comprend également la SimpleIOC cadre, mais vous pouvez la relier à d'autres personnes si vous le souhaitez.
Avec de simples CIO de vous inscrire à une mise en œuvre à l'encontre d'un type...
Dans cet exemple, le modèle de vue est créé et passé un fournisseur de services objet que par son constructeur.
Ensuite, vous créez une propriété qui retourne une instance de la COI.
L'astuce est que le modèle de vue locator est ensuite créé dans l'app.xaml ou l'équivalent, selon une source de données.
Vous pouvez maintenant lier à son MyViewModel propriété pour obtenir votre viewmodel avec l'injection d'une service.
Espère que ça aide. Toutes mes excuses pour n'importe quel code des inexactitudes, codées à partir de la mémoire sur un iPad.
GetInstance
ouresolve
à l'extérieur du lancement de l'application. C'est le point de DI !Utiliser le Managed Extensibility Framework.
En général, ce que vous voulez faire est d'avoir une classe statique et d'utiliser le Modèle de Fabrique pour vous fournir un conteneur global (mise en cache, natch).
Que pour la façon d'injecter le point de vue des modèles, vous pouvez injecter de la même façon que vous injectez tout le reste. Créer une importation constructeur (ou mettre une instruction d'importation sur une propriété/domaine) dans le code-behind du fichier XAML, et dites-lui d'importer le modèle de vue. Puis lier votre
Window
'sDataContext
à la propriété. Votre racine objets vous fait sortir du container vous-même sont généralement composéesWindow
objets. Juste ajouter des interfaces pour les classes de fenêtre, et de les exporter, puis saisir à partir du catalogue comme ci-dessus (dans l'App.xaml.cs... c'est le WPF fichier de bootstrap).new
.Canonique DryIoc cas
De répondre à un vieux post, mais de le faire avec
DryIoc
et de faire ce que je pense est une bonne utilisation de la DI et des interfaces (utilisation minimale des classes de béton).App.xaml
, et il nous dire quel est le premier de la vue à utiliser; nous le faisons avec le code derrière au lieu de la valeur par défaut xaml:StartupUri="MainWindow.xaml"
dans l'App.xamldans le code-behind (App.xaml.cs) ajouter ce
override OnStartup
:c'est le point de départ; c'est aussi le seul endroit où
resolve
doit être appelé.la configuration de la racine (selon la Marque Seeman le livre de l'injection de Dépendance dans la .NET; le seul endroit où les classes de béton doit être indiquée) sera dans le même code-behind, dans le constructeur:
Remarques et quelques détails de plus
MainWindow
;Le ViewModel constructeur avec DI:
ViewModel constructeur par défaut pour la conception:
Le code-behind de la vue:
et ce qui est nécessaire dans la vue (MainWindow.xaml) pour obtenir une instance de conception avec ViewModel:
Conclusion
Nous avons donc obtenu un très propre et minimal de la mise en œuvre d'une application WPF avec un DryIoc conteneur et DI tout en gardant le design des instances de vues et viewmodel possible.
Je vous suggérons d'utiliser le ViewModel - Première approche
https://github.com/Caliburn-Micro/Caliburn.Micro
voir:
https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions
utilisation
Castle Windsor
comme conteneur IOC.Toutes Les Conventions
L'une des principales caractéristiques de Caliburn.Micro est manifeste dans sa capacité à éliminer la nécessité pour la chaudière de la plaque de code, en agissant sur une série de conventions. Certaines personnes aiment les conventions et certains les détestent. C'est pourquoi CM conventions sont entièrement personnalisables et peuvent même être complètement éteint si elle n'est pas désirée. Si vous allez utiliser les conventions, et depuis ils sont activés par défaut, il est bon de savoir ce que ces conventions sont et comment ils fonctionnent. C'est le sujet de cet article.
Vue De La Résolution (ViewModel-Première)
Bases
La première convention, vous êtes susceptible de rencontrer lors de l'utilisation de CM est liée à la résolution de vues. La présente convention n'affecte les ViewModel-Première les domaines de votre application. Dans le ViewModel-tout d'Abord, nous avons déjà ViewModel que nous avons besoin de rendre à l'écran. Pour ce faire, CM utilise un simple modèle de nommage pour trouver un UserControl1 qu'il est conseillé de lier le ViewModel et de l'affichage. Alors, quel est ce modèle? Nous allons juste prendre un coup d'oeil à ViewLocator.LocateForModelType à savoir:
Nous allons ignorer le “contexte” de la variable au premier abord. Pour dériver le point de vue, nous faisons une hypothèse que vous utilisez le texte “ViewModel” dans le nommage de vos machines virtuelles, afin que nous venons de changer l'état de “Vue” partout où nous le trouvons en supprimant le mot “Modèle”. Cela a pour effet de modifier à la fois les noms de type et les espaces de noms. Donc Viewmodel.CustomerViewModel deviendrait points de Vue.CustomerView. Ou si vous êtes l'organisation de votre application en fonction: CustomerManagement.CustomerViewModel devient CustomerManagement.CustomerView. J'espère que c'est assez simple. Une fois que nous avons le nom, nous avons alors à la recherche pour les types avec ce nom. Nous sommes la recherche de toute l'assemblée, vous avez exposé à la CM comme consultable via AssemblySource.Exemple.2 Si nous trouvons le type, nous créons une instance (ou en obtenir un à partir du conteneur IoC si il est enregistré) et le retourner à l'appelant. Si nous n'avons pas trouver le type, nous générer une vue avec un “non trouvé” message.
Maintenant, revenir à ce “contexte” de la valeur. C'est de cette manière CM prend en charge de multiples points de Vue au cours de la même ViewModel. Si un contexte (en général une chaîne ou un enum) est fourni, nous faisons une poursuite de la transformation du nom, de la fonction de cette valeur. Cette transformation suppose que vous avez un dossier (espace de noms) pour les différents points de vue en supprimant le mot “Vue” à partir de la fin et en ajoutant le contexte de la place. Donc, étant donné un contexte de “Maître” notre Viewmodel.CustomerViewModel deviendrait points de Vue.Client.Maître.
Supprimer le démarrage de l'uri de votre application.xaml.
App.xaml.cs
Vous pouvez maintenant utiliser votre Cio classe pour construire les instances.
MainWindowView.xaml.cs
GetInstance
deresolve
en dehors de l'app.xaml.cs, vous perdez le point de DI. Aussi, mentionnant le code xaml affichage dans la vue de code est une sorte de complexe. Il suffit d'appeler la vue en pur c#, et cela avec le conteneur.