Je démarre une nouvelle application de bureau et je souhaite la créer à l'aide de MVVM et WPF.
J'ai également l'intention d'utiliser TDD.
Le problème est que je ne sais pas comment utiliser un conteneur IoC pour injecter mes dépendances sur mon code de production.
Supposons que j'ai la classe et l'interface suivantes:
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
pour dépendance, supposons aussi que cette classe soit un ViewModel ou une classe affaires ...
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
Avec cela, je peux facilement écrire des tests unitaires pour m'assurer qu'ils fonctionnent correctement, en utilisant des simulations, etc.
Le problème est quand 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 l' IStorage
interface, mais comment ferais-je cela?
Par exemple, comment serait-ce si j'avais le xaml suivant:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
Comment puis-je «dire» correctement à WPF d'injecter des dépendances dans ce cas?
De plus, supposons que j'ai besoin d'une instance de SomeViewModel
de mon code C #, comment dois-je le faire?
Je sens que je suis complètement perdu dans tout cela, j'apprécierais tout exemple ou toute indication sur la meilleure façon de gérer cela.
Je connais StructureMap, mais je ne suis pas un expert. De plus, s'il existe un cadre meilleur / plus facile / prêt à l'emploi, veuillez me le faire savoir.
la source
Réponses:
J'utilise Ninject et j'ai trouvé que c'était 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 SO).
Donc, en gros, ça va comme ça:
Créez le modèle de vue et prenez l'
IStorage
interface comme paramètre de constructeur:Créez un
ViewModelLocator
avec une propriété get pour le modèle de vue, qui charge le modèle de vue à partir de Ninject:Créez
ViewModelLocator
une ressource à l'échelle de l'application dans App.xaml:Liez le
DataContext
deUserControl
à la propriété correspondante dans ViewModelLocator.Créez une classe héritant de NinjectModule, qui mettra en place les liaisons nécessaires (
IStorage
et le viewmodel):Initialisez le noyau IoC au démarrage de l'application avec les modules Ninject nécessaires (celui ci-dessus pour l'instant):
J'ai utilisé une
IocKernel
classe statique pour contenir l'instance du noyau IoC à l'échelle de l'application, afin de pouvoir y accéder facilement en cas de besoin:Cette solution utilise un static
ServiceLocator
(theIocKernel
), qui est généralement considéré comme un anti-pattern, car il cache les dépendances de la classe. Cependant, il est très difficile d'éviter une sorte de recherche manuelle de services pour les classes d'interface utilisateur, car elles doivent avoir un constructeur sans paramètre et vous ne pouvez pas contrôler l'instanciation de toute façon, vous ne pouvez donc pas injecter la machine virtuelle. Au moins, cette méthode vous permet de tester la machine virtuelle de manière isolée, où se trouve toute la logique métier.Si quelqu'un a un meilleur moyen, partagez-le.
EDIT: Lucky Likey a fourni une réponse pour se débarrasser du localisateur de service statique, en laissant Ninject instancier les classes d'interface utilisateur. Les détails de la réponse peuvent être consultés ici
la source
DataContext="{Binding [...]}"
. Cela oblige VS-Designer à exécuter tout le code de programme dans le constructeur du ViewModel. Dans mon cas, la fenêtre est exécutée et bloque modalement toute interaction avec VS. Peut-être devrait-on modifier le ViewModelLocator pour ne pas localiser les "vrais" ViewModels au moment du design. - Une autre solution consiste à "Désactiver le code du projet", ce qui empêchera également tout le reste d'être affiché. Peut-être avez-vous déjà trouvé une solution intéressante à cela. Dans ce cas, je vous ferais plaisir de le montrer.Dans votre question, vous définissez la valeur de la
DataContext
propriété de la vue en XAML. Cela nécessite que votre modèle de vue ait un constructeur par défaut. Cependant, comme vous l'avez noté, cela ne fonctionne pas bien avec l'injection de dépendances où vous souhaitez injecter des dépendances dans le constructeur.Donc , vous ne pouvez pas définir la
DataContext
propriété en XAML . Au lieu de cela, vous avez d'autres alternatives.Si votre application est basée sur un modèle de vue hiérarchique simple, vous pouvez construire la hiérarchie complète du modèle de vue au démarrage de l'application (vous devrez supprimer la
StartupUri
propriété duApp.xaml
fichier):Ceci est basé sur un graphique d'objet de modèles de vue enraciné au niveau du,
RootViewModel
mais vous pouvez injecter des fabriques de modèles de vue dans des modèles de vue parents, ce qui leur permet de créer de nouveaux modèles de vue enfants afin que le graphique d'objet n'ait pas à être corrigé. J'espère que cela répondra également à votre question en supposant que j'ai besoin d'une instance deSomeViewModel
de moncs
code, comment dois-je le faire?Si votre application est de nature plus dynamique et est peut-être basée sur la navigation, vous devrez vous connecter au code qui effectue la navigation. Chaque fois que vous naviguez vers une nouvelle vue, vous devez créer un modèle de vue (à partir du conteneur DI), la vue elle-même et définir
DataContext
la vue sur le modèle de vue. Vous pouvez faire cette vue d'abord là où vous choisissez un modèle de vue basé sur une vue ou vous pouvez le faire d' abord modèle de vueoù le modèle de vue détermine la vue à utiliser. Un framework MVVM fournit cette fonctionnalité clé avec un moyen pour vous de connecter votre conteneur DI à la création de modèles de vue, mais vous pouvez également l'implémenter 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 d'un framework MVVM, mais déployer la vôtre dans une application simple vous donnera une bonne compréhension de ce que les frameworks MVVM fournissent sous le capot.En ne pouvant pas déclarer le
DataContext
en XAML, vous perdez une partie de la prise en charge au moment du design. Si votre modèle de vue contient des données, il apparaîtra pendant la conception, ce qui peut être très utile. Heureusement, vous pouvez également utiliser des attributs au moment du design dans WPF. Une façon de procéder consiste à ajouter les attributs suivants à l'<Window>
élément ou<UserControl>
en XAML:Le type de modèle de vue doit avoir deux constructeurs, celui par défaut pour les données au moment du design et un autre pour l'injection de dépendances:
En faisant cela, vous pouvez utiliser l'injection de dépendances et conserver une bonne prise en charge au moment du design.
la source
Ce que je poste ici est une amélioration de la réponse de sondergard, car ce que je vais dire ne rentre pas dans un commentaire :)
En fait, je présente une solution soignée, qui évite le besoin d'un ServiceLocator et d'un wrapper pour le
StandardKernel
-Instance, qui dans la solution de sondergard s'appelleIocContainer
. Pourquoi? Comme mentionné, ce sont des anti-modèles.Rendre le
StandardKernel
disponible partoutLa clé de la magie de Ninject est la
StandardKernel
-Instance qui est nécessaire pour utiliser la.Get<T>()
-Méthode.Alternativement à sondergard,
IocContainer
vous pouvez créer l'StandardKernel
intérieur de laApp
-Classe.Supprimez simplement StartUpUri de votre App.xaml
Ceci est le CodeBehind de l'application dans App.xaml.cs
Désormais, Ninject est vivant et prêt à se battre :)
Injecter votre
DataContext
Comme Ninject est vivant, vous pouvez effectuer toutes sortes d'injections, par exemple l'injection de Property Setter ou l' injection de constructeur la plus courante .
Voici comment vous injectez votre ViewModel dans vos
Window
« sDataContext
Bien sûr, vous pouvez également Injecter un
IViewModel
si vous faites les bonnes liaisons, mais cela ne fait pas partie de cette réponse.Accéder directement au noyau
Si vous avez besoin d'appeler directement des méthodes sur le noyau (par exemple
.Get<T>()
-Method), vous pouvez laisser le noyau s'injecter lui-même.Si vous avez besoin d'une instance locale du noyau, vous pouvez l'injecter en tant que propriété.
Bien que cela puisse être très utile, je ne vous recommanderais pas de le faire. Notez simplement que les objets injectés de cette manière ne seront pas disponibles dans le constructeur, car ils seront injectés plus tard.
Selon ce lien, vous devez utiliser l'extension d'usine au lieu d'injecter le
IKernel
(DI Container).La manière dont Ninject.Extensions.Factory doit être utilisée peut également être indiquée en rouge ici .
la source
Ninject.Extensions.Factory
, indiquez-le ici dans les commentaires et j'ajouterai plus d'informations.DependencyProperty
champ de sauvegarde et ses méthodes Get et Set.J'opte pour une approche "vue d'abord", où je passe le modèle de vue au constructeur de la vue (dans son code-behind), qui est assigné au contexte de données, par exemple
Cela remplace votre approche basée sur XAML.
J'utilise le framework Prism pour gérer la navigation - quand un code demande qu'une vue particulière soit affichée (en "naviguant" dessus), Prism résoudra cette vue (en interne, en utilisant le framework DI de l'application); le framework DI résoudra à son tour toutes les dépendances de la vue (le modèle de vue dans mon exemple), puis résoudra ses dépendances, et ainsi de suite.
Le choix du framework DI est à peu près hors de propos car ils font tous essentiellement la même chose, c'est-à-dire que vous enregistrez une interface (ou un type) avec le type concret que vous voulez que le framework instancie quand il trouve une dépendance sur cette interface. Pour mémoire, j'utilise Castle Windsor.
La navigation Prism prend un certain temps pour s'y habituer, mais elle est plutôt bonne une fois que vous avez compris, vous permettant de composer votre application en utilisant différentes vues. Par exemple, vous pouvez créer une "région" Prism sur votre fenêtre principale, puis en utilisant la navigation Prism, vous passez d'une vue à une autre dans cette région, par exemple lorsque l'utilisateur sélectionne des éléments de menu ou autre.
Vous pouvez également jeter un œil à l'un des frameworks MVVM tels que MVVM Light. Je n'ai aucune expérience de ceux-ci, je ne peux donc pas commenter ce qu'ils aiment utiliser.
la source
Installez MVVM Light.
Une partie de l'installation consiste à créer un localisateur de modèle de vue. Il s'agit d'une classe qui expose vos modèles de vue en tant que propriétés. Le getter de ces propriétés peut ensuite être des instances renvoyées à partir de votre moteur IOC. Heureusement, MVVM light inclut également le framework SimpleIOC, mais vous pouvez en connecter d'autres si vous le souhaitez.
Avec un simple IOC, vous enregistrez une implémentation par rapport à un type ...
Dans cet exemple, votre modèle de vue est créé et transmis à un objet de fournisseur de services selon son constructeur.
Vous créez ensuite une propriété qui renvoie une instance d'IOC.
La partie intelligente est que le localisateur de modèle de vue est ensuite créé dans app.xaml ou équivalent en tant que source de données.
Vous pouvez maintenant vous lier à sa propriété 'MyViewModel' pour obtenir votre viewmodel avec un service injecté.
J'espère que cela pourra aider. Toutes mes excuses pour les inexactitudes de code, codées à partir de la mémoire sur un iPad.
la source
GetInstance
ou enresolve
dehors du bootstrap de l'application. C'est le but de DI!Étui Canonic DryIoc
Répondre à un ancien message, mais faire cela avec
DryIoc
et faire ce que je pense est une bonne utilisation de la DI et des interfaces (utilisation minimale de classes concrètes).App.xaml
, et là, nous disons quelle est la vue initiale à utiliser; nous faisons cela avec du code derrière au lieu du xaml par défaut:StartupUri="MainWindow.xaml"
dans App.xamldans codebehind (App.xaml.cs) ajoutez ceci
override OnStartup
:c'est le point de départ; c'est aussi le seul endroit où
resolve
il faut appeler.la racine de configuration (selon le livre de Mark Seeman Dependency injection in .NET; le seul endroit où les classes concrètes devraient être mentionnées) sera dans le même code derrière, dans le constructeur:
Remarques et quelques détails supplémentaires
MainWindow
;Le constructeur ViewModel avec DI:
Constructeur par défaut de ViewModel pour la conception:
Le code derrière 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 une implémentation très propre et minimale d'une application WPF avec un conteneur DryIoc et une DI tout en gardant les instances de conception de vues et de modèles de vue possibles.
la source
Utilisez l' infrastructure d'extensibilité gérée .
En général, ce que vous feriez est d'avoir une classe statique et d'utiliser le modèle d'usine pour vous fournir un conteneur global (mis en cache, natch).
Quant à la façon d'injecter les modèles de vue, vous les injectez de la même manière que vous injectez tout le reste. Créez un constructeur d'importation (ou placez une instruction d'importation sur une propriété / un champ) dans le code-behind du fichier XAML et indiquez-lui d'importer le modèle de vue. Rabattre ensuite votre
Window
estDataContext
à cette propriété. Les objets racine que vous retirez vous-même du conteneur sont généralement desWindow
objets composés . Ajoutez simplement des interfaces aux classes de fenêtres et exportez-les, puis récupérez-les dans le catalogue comme ci-dessus (dans App.xaml.cs ... c'est le fichier de démarrage WPF).la source
new
.Je suggérerais d'utiliser le ViewModel - Première approche https://github.com/Caliburn-Micro/Caliburn.Micro
voir: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions
utiliser
Castle Windsor
comme conteneur IOC.Tout sur les conventions
L'une des principales caractéristiques de Caliburn.Micro est manifeste dans sa capacité à supprimer le besoin de code de plaque de chaudière en agissant sur une série de conventions. Certaines personnes aiment les conventions et d'autres les détestent. C'est pourquoi les conventions de CM sont entièrement personnalisables et peuvent même être complètement désactivées si elles ne sont pas souhaitées. Si vous comptez utiliser des conventions, et comme elles sont activées par défaut, il est bon de savoir quelles sont ces conventions et comment elles fonctionnent. C'est le sujet de cet article. Résolution de la vue (ViewModel-First)
Basiques
La première convention que vous êtes susceptible de rencontrer lors de l'utilisation de CM est liée à la résolution de la vue. Cette convention affecte toutes les zones ViewModel-First de votre application. Dans ViewModel-First, nous avons un ViewModel existant que nous devons rendre à l'écran. Pour ce faire, CM utilise un modèle de dénomination simple pour trouver un UserControl1 qu'il doit lier au ViewModel et afficher. Alors, quel est ce modèle? Jetons un coup d'œil à ViewLocator.LocateForModelType pour découvrir:
Ignorons d'abord la variable «context». Pour dériver la vue, nous partons du principe que vous utilisez le texte «ViewModel» dans la dénomination de vos VM, donc nous changeons simplement cela en «View» partout où nous le trouvons en supprimant le mot «Model». Cela a pour effet de changer les noms de type et les espaces de noms. Donc ViewModels.CustomerViewModel deviendrait Views.CustomerView. Ou si vous organisez votre application par fonctionnalité: CustomerManagement.CustomerViewModel devient CustomerManagement.CustomerView. J'espère que c'est assez simple. Une fois que nous avons le nom, nous recherchons les types avec ce nom. Nous recherchons tout assembly que vous avez exposé à CM comme pouvant être recherché via AssemblySource.Instance.2 Si nous trouvons le type, nous créons une instance (ou en obtenons une dans le conteneur IoC s'il est enregistré) et la renvoyons à l'appelant. Si nous ne trouvons pas le type,
Maintenant, revenons à cette valeur de «contexte». C'est ainsi que CM prend en charge plusieurs vues sur le même ViewModel. Si un contexte (généralement une chaîne ou une énumération) est fourni, nous effectuons une transformation supplémentaire du nom, en fonction de cette valeur. Cette transformation suppose en fait que vous avez un dossier (espace de noms) pour les différentes vues en supprimant le mot «Vue» à la fin et en ajoutant le contexte à la place. Ainsi, étant donné un contexte de «Master», nos ViewModels.CustomerViewModel deviendraient Views.Customer.Master.
la source
Supprimez l'URI de démarrage de votre app.xaml.
App.xaml.cs
Vous pouvez maintenant utiliser votre classe IoC pour construire les instances.
MainWindowView.xaml.cs
la source
GetInstance
d'resolve
app.xaml.cs extérieur, vous perdez le point de DI. De plus, mentionner la vue xaml dans le code de la vue est un peu compliqué. Appelez simplement la vue en c # pur et faites-le avec le conteneur.