Ich starte eine neue Desktop-Anwendung und möchte sie mit MVVM und WPF erstellen.
Ich beabsichtige auch, TDD zu verwenden.
Das Problem ist, dass ich nicht weiß, wie ich einen IoC-Container verwenden soll, um meine Abhängigkeiten von meinem Produktionscode einzufügen.
Angenommen, ich habe die folgende Klasse und Schnittstelle:
public interface IStorage
{
bool SaveFile(string content);
}
public class Storage : IStorage
{
public bool SaveFile(string content){
// Saves the file using StreamWriter
}
}
Und dann habe ich eine andere Klasse, die IStorage
eine Abhängigkeit hat. Nehmen wir auch an, dass diese Klasse ein ViewModel oder eine Business-Klasse ist ...
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
Mit diesem kann ich leicht Unit-Tests schreiben, um sicherzustellen, dass sie richtig funktionieren, mit Mocks und so weiter.
Das Problem ist, wenn es darum geht, es in der realen Anwendung zu verwenden. Ich weiß, dass ich einen IoC-Container haben muss, der eine Standardimplementierung für die IStorage
Schnittstelle verknüpft , aber wie würde ich das tun?
Wie wäre es zum Beispiel, wenn ich das folgende xaml hätte:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
Wie kann ich WPF in diesem Fall korrekt anweisen, Abhängigkeiten einzufügen?
Angenommen, ich benötige eine Instanz SomeViewModel
von meinem C # -Code. Wie soll ich das tun?
Ich fühle mich völlig verloren, ich würde mich über jedes Beispiel oder eine Anleitung freuen, wie man am besten damit umgeht.
Ich bin mit StructureMap vertraut, aber kein Experte. Wenn es ein besseres / einfacheres / sofort einsatzbereites Framework gibt, lassen Sie es mich bitte wissen.
quelle
Antworten:
Ich habe Ninject verwendet und festgestellt, dass es eine Freude ist, mit mir zu arbeiten. Alles ist im Code eingerichtet, die Syntax ist ziemlich einfach und es gibt eine gute Dokumentation (und viele Antworten auf SO).
Im Grunde geht es so:
Erstellen Sie das Ansichtsmodell und nehmen Sie die
IStorage
Schnittstelle als Konstruktorparameter:Erstellen Sie eine
ViewModelLocator
Eigenschaft mit dem Namen get für das Ansichtsmodell, mit der das Ansichtsmodell aus Ninject geladen wird:Machen Sie
ViewModelLocator
eine anwendungsweite Ressource in App.xaml:Binden Sie das
DataContext
vonUserControl
an die entsprechende Eigenschaft im ViewModelLocator.Erstellen Sie eine Klasse, die NinjectModule erbt, und richtet die erforderlichen Bindungen (
IStorage
und das Ansichtsmodell) ein:Initialisieren Sie den IoC-Kernel beim Start der Anwendung mit den erforderlichen Ninject-Modulen (das oben genannte):
Ich habe eine statische
IocKernel
Klasse verwendet, um die anwendungsweite Instanz des IoC-Kernels zu speichern, sodass ich bei Bedarf problemlos darauf zugreifen kann:Diese Lösung verwendet eine statische
ServiceLocator
(dieIocKernel
), das allgemein als Anti-Pattern angesehen wird, da es die Abhängigkeiten der Klasse verbirgt. Es ist jedoch sehr schwierig, eine manuelle Service-Suche für UI-Klassen zu vermeiden, da diese einen parameterlosen Konstruktor haben müssen und Sie die Instanziierung ohnehin nicht steuern können, sodass Sie die VM nicht injizieren können. Zumindest auf diese Weise können Sie die VM isoliert testen. Hier befindet sich die gesamte Geschäftslogik.Wenn jemand einen besseren Weg hat, teilen Sie bitte.
BEARBEITEN: Lucky Likey gab eine Antwort, um den statischen Service Locator loszuwerden, indem Ninject UI-Klassen instanziieren ließ. Die Details der Antwort können hier eingesehen werden
quelle
DataContext="{Binding [...]}"
. Dies führt dazu, dass der VS-Designer den gesamten Programmcode im ViewModel-Konstruktor ausführt. In meinem Fall wird das Fenster ausgeführt und blockiert modal jede Interaktion mit VS. Vielleicht sollte man den ViewModelLocator so ändern, dass die "echten" ViewModels in der Entwurfszeit nicht gefunden werden. - Eine andere Lösung ist das Deaktivieren des Projektcodes, wodurch auch verhindert wird, dass alles andere angezeigt wird. Vielleicht haben Sie bereits eine gute Lösung dafür gefunden. In diesem Fall würde ich Sie bitten, es zu zeigen.In Ihrer Frage legen Sie den Wert der
DataContext
Eigenschaft der Ansicht in XAML fest. Dies erfordert, dass Ihr Ansichtsmodell einen Standardkonstruktor hat. Wie Sie bereits bemerkt haben, funktioniert dies jedoch nicht gut mit der Abhängigkeitsinjektion, bei der Sie Abhängigkeiten in den Konstruktor einfügen möchten.So Sie nicht festlegen können
DataContext
Eigenschaft in XAML . Stattdessen haben Sie andere Alternativen.Wenn Ihre Anwendung auf einem einfachen hierarchischen Ansichtsmodell basiert, können Sie beim Start der Anwendung die gesamte Ansichtsmodellhierarchie erstellen (Sie müssen die
StartupUri
Eigenschaft aus derApp.xaml
Datei entfernen ):Dies basiert auf einem Objektdiagramm von Ansichtsmodellen, die auf dem verwurzelt sind.
RootViewModel
Sie können jedoch einige Ansichtsmodellfabriken in übergeordnete Ansichtsmodelle einfügen, sodass diese neue untergeordnete Ansichtsmodelle erstellen können, sodass das Objektdiagramm nicht repariert werden muss. Dies beantwortet hoffentlich auch Ihre Frage. Angenommen, ich benötige eine InstanzSomeViewModel
aus meinemcs
Code. Wie soll ich das tun?Wenn Ihre Anwendung dynamischer ist und möglicherweise auf der Navigation basiert, müssen Sie sich in den Code einbinden, der die Navigation ausführt. Jedes Mal, wenn Sie zu einer neuen Ansicht navigieren, müssen Sie ein Ansichtsmodell (aus dem DI-Container) erstellen, die Ansicht selbst und
DataContext
die Ansicht auf das Ansichtsmodell setzen. Sie können diese Ansicht zuerst ausführen, indem Sie ein Ansichtsmodell basierend auf einer Ansicht auswählen, oder Sie können es zuerst als Ansichtsmodell ausführenDabei bestimmt das Ansichtsmodell, welche Ansicht verwendet werden soll. Ein MVVM-Framework bietet dieser Schlüsselfunktionalität eine Möglichkeit, Ihren DI-Container in die Erstellung von Ansichtsmodellen einzubinden, Sie können ihn jedoch auch selbst implementieren. Ich bin hier etwas vage, da diese Funktionalität je nach Ihren Anforderungen sehr komplex werden kann. Dies ist eine der Kernfunktionen, die Sie von einem MVVM-Framework erhalten. Wenn Sie jedoch Ihre eigenen Funktionen in einer einfachen Anwendung verwenden, erhalten Sie ein gutes Verständnis dafür, was MVVM-Frameworks unter der Haube bieten.Wenn
DataContext
Sie das in XAML nicht deklarieren können, verlieren Sie Unterstützung für die Entwurfszeit. Wenn Ihr Ansichtsmodell einige Daten enthält, werden diese während der Entwurfszeit angezeigt, was sehr nützlich sein kann. Glücklicherweise können Sie Entwurfszeitattribute auch in WPF verwenden. Eine Möglichkeit, dies zu tun, besteht darin, dem<Window>
Element oder<UserControl>
in XAML die folgenden Attribute hinzuzufügen :Der Ansichtsmodelltyp sollte zwei Konstruktoren haben, den Standard für Entwurfszeitdaten und einen für die Abhängigkeitsinjektion:
Auf diese Weise können Sie die Abhängigkeitsinjektion verwenden und eine gute Unterstützung für die Entwurfszeit beibehalten.
quelle
Was ich hier poste, ist eine Verbesserung von Sondergards Antwort, weil das, was ich erzählen werde, nicht in einen Kommentar passt :)
Tatsächlich stelle ich eine saubere Lösung vor, bei der kein ServiceLocator und kein Wrapper für die
StandardKernel
-Instance erforderlich sind, die in sondergards Lösung genannt wirdIocContainer
. Warum? Wie bereits erwähnt, sind dies Anti-Muster.Making the
StandardKernel
überall verfügbarDer Schlüssel zu Ninjects Magie ist die
StandardKernel
-Instanz, die zur Verwendung der.Get<T>()
-Methode benötigt wird.Alternativ zu Sondergards
IocContainer
können Sie dasStandardKernel
Innere derApp
-Klasse erstellen.Entfernen Sie einfach StartUpUri aus Ihrer App.xaml
Dies ist der CodeBehind der App in App.xaml.cs
Von nun an lebt Ninject und ist bereit zu kämpfen :)
Injizieren Sie Ihre
DataContext
Da Ninject am Leben ist, können Sie alle Arten von Injektionen durchführen, z. B. Property Setter Injection oder die häufigste Constructor Injection .
Dies ist , wie Sie Ihre Ansichtsmodell in Ihre injizieren
Window
‚sDataContext
Natürlich können Sie auch eine injizieren
IViewModel
wenn Sie die richtigen Bindungen ausführen, aber das ist nicht Teil dieser Antwort.Direkter Zugriff auf den Kernel
Wenn Sie Methoden direkt auf dem Kernel aufrufen müssen (z. B.
.Get<T>()
-Method), können Sie den Kernel selbst injizieren lassen.Wenn Sie eine lokale Instanz des Kernels benötigen, können Sie diese als Eigenschaft einfügen.
Obwohl dies sehr nützlich sein kann, würde ich Ihnen dies nicht empfehlen. Beachten Sie nur, dass auf diese Weise injizierte Objekte im Konstruktor nicht verfügbar sind, da sie später injiziert werden.
Gemäß diesem Link sollten Sie die Werkserweiterung verwenden, anstatt den
IKernel
(DI-Container) zu injizieren .Wie die Ninject.Extensions.Factory verwendet werden soll , kann auch rot sein hier .
quelle
Ninject.Extensions.Factory
, gib es hier in den Kommentaren an und ich werde weitere Informationen hinzufügen.DependencyProperty
als auch die Methoden Get und Set.Ich gehe zu einem "View First" -Ansatz, bei dem ich das Ansichtsmodell an den Konstruktor der Ansicht (in seinem Code-Behind) übergebe, der dem Datenkontext zugewiesen wird, z
Dies ersetzt Ihren XAML-basierten Ansatz.
Ich verwende das Prism-Framework, um die Navigation zu handhaben. Wenn einige Codeanforderungen eine bestimmte Ansicht anzeigen (durch "Navigieren"), löst Prism diese Ansicht auf (intern unter Verwendung des DI-Frameworks der App). Das DI-Framework löst wiederum alle Abhängigkeiten auf, die die Ansicht aufweist (das Ansichtsmodell in meinem Beispiel), löst dann ihre Abhängigkeiten auf und so weiter.
Die Wahl des DI-Frameworks ist ziemlich irrelevant, da alle im Wesentlichen dasselbe tun, dh Sie registrieren eine Schnittstelle (oder einen Typ) zusammen mit dem konkreten Typ, den das Framework instanziieren soll, wenn es eine Abhängigkeit von dieser Schnittstelle findet. Für die Aufzeichnung benutze ich Castle Windsor.
Die Prismanavigation ist gewöhnungsbedürftig, aber ziemlich gut, wenn Sie sich erst einmal damit vertraut gemacht haben, sodass Sie Ihre Anwendung mit verschiedenen Ansichten erstellen können. Sie können beispielsweise eine Prisma-Region in Ihrem Hauptfenster erstellen und dann mithilfe der Prisma-Navigation innerhalb dieser Region von einer Ansicht zur nächsten wechseln, z. B. wenn der Benutzer Menüelemente oder was auch immer auswählt.
Alternativ können Sie sich eines der MVVM-Frameworks wie MVVM Light ansehen. Ich habe keine Erfahrung mit diesen, kann also nicht kommentieren, wie sie verwendet werden sollen.
quelle
Installieren Sie MVVM Light.
Teil der Installation ist das Erstellen eines Ansichtsmodell-Locators. Dies ist eine Klasse, die Ihre Ansichtsmodelle als Eigenschaften verfügbar macht. Der Getter dieser Eigenschaften kann dann Instanzen von Ihrer IOC-Engine zurückgegeben werden. Glücklicherweise enthält MVVM light auch das SimpleIOC-Framework, aber Sie können auch andere verkabeln, wenn Sie möchten.
Mit einfachem IOC registrieren Sie eine Implementierung für einen Typ ...
In diesem Beispiel wird Ihr Ansichtsmodell erstellt und ein Dienstanbieterobjekt gemäß seinem Konstruktor übergeben.
Anschließend erstellen Sie eine Eigenschaft, die eine Instanz vom IOC zurückgibt.
Der clevere Teil ist, dass der View Model Locator dann in app.xaml oder einem gleichwertigen Element als Datenquelle erstellt wird.
Sie können jetzt an die Eigenschaft 'MyViewModel' binden, um Ihr Ansichtsmodell mit einem injizierten Dienst abzurufen.
Hoffentlich hilft das. Entschuldigung für etwaige Code-Ungenauigkeiten, die auf einem iPad aus dem Speicher codiert wurden.
quelle
GetInstance
oderresolve
außerhalb des Bootstraps der Anwendung haben. Das ist der Punkt von DI!Canonic DryIoc Fall
Das Beantworten eines alten Beitrags, aber dies zu
DryIoc
tun und das zu tun, was ich denke, ist eine gute Verwendung von DI und Schnittstellen (minimale Verwendung konkreter Klassen).App.xaml
, und dort erklären wir, welche ursprüngliche Ansicht verwendet werden soll. Wir machen das mit Code dahinter anstelle des Standard-xaml:StartupUri="MainWindow.xaml"
in App.xaml entfernenFügen Sie in codebehind (App.xaml.cs) Folgendes hinzu
override OnStartup
:das ist der Startpunkt; das ist auch der einzige Ort, an dem
resolve
angerufen werden sollte.Das Konfigurationsstammverzeichnis (gemäß Mark Seemans Buch Dependency Injection in .NET; der einzige Ort, an dem konkrete Klassen erwähnt werden sollten) befindet sich im Konstruktor im selben Codebehind:
Anmerkungen und einige weitere Details
MainWindow
;Der ViewModel-Konstruktor mit DI:
ViewModel-Standardkonstruktor für Design:
Der Code hinter der Ansicht:
und was in der Ansicht (MainWindow.xaml) benötigt wird, um eine Entwurfsinstanz mit ViewModel zu erhalten:
Fazit
Wir haben daher eine sehr saubere und minimale Implementierung einer WPF-Anwendung mit einem DryIoc-Container und DI erhalten, während Entwurfsinstanzen von Ansichten und Ansichtsmodellen möglich sind.
quelle
Verwenden Sie das Managed Extensibility Framework .
Im Allgemeinen haben Sie eine statische Klasse und verwenden das Factory-Muster, um einen globalen Container (zwischengespeichert, natch) bereitzustellen.
Wie Sie die Ansichtsmodelle injizieren, injizieren Sie auf dieselbe Weise wie alles andere. Erstellen Sie einen Importkonstruktor (oder fügen Sie eine Importanweisung in eine Eigenschaft / ein Feld ein) im CodeBehind der XAML-Datei und weisen Sie ihn an, das Ansichtsmodell zu importieren. Dann binden Sie Ihre
Window
‚sDataContext
zu dieser Eigenschaft. Ihre Stammobjekte, die Sie tatsächlich selbst aus dem Container ziehen, sind normalerweise zusammengesetztWindow
Objekte. Fügen Sie den Fensterklassen einfach Schnittstellen hinzu, exportieren Sie sie und greifen Sie dann wie oben beschrieben aus dem Katalog zu (in App.xaml.cs ... das ist die WPF-Bootstrap-Datei).quelle
new
.Ich würde vorschlagen, das ViewModel - First Approach https://github.com/Caliburn-Micro/Caliburn.Micro zu verwenden
sehen: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions
verwenden
Castle Windsor
als IOC-Container.Alles über Konventionen
Eines der Hauptmerkmale von Caliburn.Micro zeigt sich in seiner Fähigkeit, die Notwendigkeit eines Kesselplattencodes durch Befolgen einer Reihe von Konventionen zu beseitigen. Manche Menschen lieben Konventionen und manche hassen sie. Aus diesem Grund sind die Konventionen von CM vollständig anpassbar und können auf Wunsch sogar vollständig deaktiviert werden. Wenn Sie Konventionen verwenden möchten und diese standardmäßig aktiviert sind, ist es gut zu wissen, was diese Konventionen sind und wie sie funktionieren. Das ist das Thema dieses Artikels. Ansichtsauflösung (ViewModel-First)
Grundlagen
Die erste Konvention, auf die Sie bei der Verwendung von CM wahrscheinlich stoßen, bezieht sich auf die Auflösung der Ansicht. Diese Konvention betrifft alle ViewModel-First-Bereiche Ihrer Anwendung. In ViewModel-First verfügen wir über ein vorhandenes ViewModel, das auf dem Bildschirm gerendert werden muss. Zu diesem Zweck verwendet CM ein einfaches Namensmuster, um ein UserControl1 zu finden, das an das ViewModel gebunden und angezeigt werden soll. Also, was ist das für ein Muster? Schauen wir uns einfach ViewLocator.LocateForModelType an, um herauszufinden:
Ignorieren wir zunächst die Kontextvariable. Um die Ansicht abzuleiten, gehen wir davon aus, dass Sie bei der Benennung Ihrer VMs den Text "ViewModel" verwenden. Daher ändern wir diesen einfach überall dort, wo wir ihn finden, in "Ansicht", indem wir das Wort "Modell" entfernen. Dies hat zur Folge, dass sowohl Typnamen als auch Namespaces geändert werden. Aus ViewModels.CustomerViewModel würde also Views.CustomerView. Oder wenn Sie Ihre Anwendung nach Funktionen organisieren: CustomerManagement.CustomerViewModel wird zu CustomerManagement.CustomerView. Hoffentlich ist das ziemlich einfach. Sobald wir den Namen haben, suchen wir nach Typen mit diesem Namen. Wir durchsuchen jede Assembly, die Sie CM zur Verfügung gestellt haben, als durchsuchbar über AssemblySource.Instance.2. Wenn wir den Typ finden, erstellen wir eine Instanz (oder holen eine aus dem IoC-Container, wenn sie registriert ist) und geben sie an den Aufrufer zurück. Wenn wir den Typ nicht finden,
Nun zurück zu diesem "Kontext" -Wert. Auf diese Weise unterstützt CM mehrere Ansichten über dasselbe ViewModel. Wenn ein Kontext (normalerweise eine Zeichenfolge oder eine Aufzählung) angegeben wird, führen wir eine weitere Transformation des Namens basierend auf diesem Wert durch. Bei dieser Umwandlung wird effektiv davon ausgegangen, dass Sie einen Ordner (Namespace) für die verschiedenen Ansichten haben, indem Sie das Wort "Ansicht" am Ende entfernen und stattdessen den Kontext anhängen. In einem Kontext von „Master“ würde unser ViewModels.CustomerViewModel zu Views.Customer.Master.
quelle
Entfernen Sie die Start-URL aus Ihrer app.xaml.
App.xaml.cs
Jetzt können Sie Ihre IoC-Klasse verwenden, um die Instanzen zu erstellen.
MainWindowView.xaml.cs
quelle
GetInstance
vonresolve
außen App.xaml.cs, können Sie den Punkt von DI sind zu verlieren. Auch die Erwähnung der xaml-Ansicht im Codebehind der Ansicht ist etwas kompliziert. Rufen Sie einfach die Ansicht in reinem c # auf und tun Sie dies mit dem Container.