So behandeln Sie die Abhängigkeitsinjektion in einer WPF / MVVM-Anwendung


101

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 IStorageeine 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 IStorageSchnittstelle 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 SomeViewModelvon 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.


Mit .net Core 3.0 in der Vorschau können Sie dies mit einigen Microsoft Nuget-Paketen tun.
Bailey Miller

Antworten:


87

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 IStorageSchnittstelle als Konstruktorparameter:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Erstellen Sie eine ViewModelLocatorEigenschaft mit dem Namen get für das Ansichtsmodell, mit der das Ansichtsmodell aus Ninject geladen wird:

class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

Machen Sie ViewModelLocatoreine anwendungsweite Ressource in App.xaml:

<Application ...>
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

Binden Sie das DataContextvon UserControlan die entsprechende Eigenschaft im ViewModelLocator.

<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

Erstellen Sie eine Klasse, die NinjectModule erbt, und richtet die erforderlichen Bindungen ( IStorageund das Ansichtsmodell) ein:

class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

Initialisieren Sie den IoC-Kernel beim Start der Anwendung mit den erforderlichen Ninject-Modulen (das oben genannte):

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

Ich habe eine statische IocKernelKlasse verwendet, um die anwendungsweite Instanz des IoC-Kernels zu speichern, sodass ich bei Bedarf problemlos darauf zugreifen kann:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

    public static void Initialize(params INinjectModule[] modules)
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(modules);
        }
    }
}

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


13
Ich bin neu in der Abhängigkeitsinjektion, aber im Kern kombiniert Ihre Lösung das Anti-Pattern des Service Locator mit dem Ninject, da Sie den statischen ViewModel Locator verwenden. Man könnte argumentieren, dass die Injektion in eine Xaml-Datei erfolgt, die mit geringerer Wahrscheinlichkeit getestet wird. Ich habe keine bessere Lösung und werde wahrscheinlich Ihre verwenden - aber ich denke, es wäre hilfreich, dies auch in der Antwort zu erwähnen.
user3141326

Mann, deine Lösung ist einfach großartig, es gibt nur ein "Problem" mit der folgenden Zeile : 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.
LuckyLikey

@LuckyLikey Sie können versuchen, d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" zu verwenden, aber ich bin nicht sicher, ob es einen Unterschied macht. Aber warum / wie startet der VM-Konstruktor ein modales Fenster? Und was für ein Fenster?
Sondergard

@son Eigentlich weiß ich nicht warum und wie, aber wenn ich einen Fenster-Designer über den Projektmappen-Explorer öffne, wird beim Öffnen des neuen Tabs das Fenster vom Designer angezeigt und das gleiche Fenster wird angezeigt, als ob Sie modal debuggen würden. in einem neuen Prozess außerhalb von VS "Micorosoft Visual Studio XAML Designer" gehostet. Wenn der Prozess heruntergefahren wird, schlägt auch der VS-Designer mit der zuvor genannten Ausnahme fehl. Ich werde versuchen, Ihre Problemumgehung. Ich werde Sie benachrichtigen, wenn ich neue
Informationen

1
@sondergard Ich habe eine Verbesserung Ihrer Antwort veröffentlicht, um das ServiceLocator Anti-Pattern zu vermeiden. Fühlen Sie sich frei, es auszuprobieren.
LuckyLikey

52

In Ihrer Frage legen Sie den Wert der DataContextEigenschaft 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 DataContextEigenschaft 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 StartupUriEigenschaft aus der App.xamlDatei entfernen ):

public partial class App {

  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve<RootViewModel>();
    var window = new MainWindow { DataContext = viewModel };
    window.Show();
  }

}

Dies basiert auf einem Objektdiagramm von Ansichtsmodellen, die auf dem verwurzelt sind. RootViewModelSie 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 Instanz SomeViewModelaus meinem csCode. Wie soll ich das tun?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

  public void AddChild() {
    Children.Add(_childViewModelFactory.Create());
  }

  ObservableCollection<ChildViewModel> Children { get; private set; }

 }

class ChildViewModelFactory {

  public ChildViewModelFactory(/* ChildViewModel dependencies */) {
    // Store dependencies.
  }

  public ChildViewModel Create() {
    return new ChildViewModel(/* Use stored dependencies */);
  }

}

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 DataContextdie 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 DataContextSie 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 :

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

Der Ansichtsmodelltyp sollte zwei Konstruktoren haben, den Standard für Entwurfszeitdaten und einen für die Abhängigkeitsinjektion:

class MyViewModel : INotifyPropertyChanged {

  public MyViewModel() {
    // Create some design-time data.
  }

  public MyViewModel(/* Dependencies */) {
    // Store dependencies.
  }

}

Auf diese Weise können Sie die Abhängigkeitsinjektion verwenden und eine gute Unterstützung für die Entwurfszeit beibehalten.


12
Genau das habe ich gesucht. Es frustriert mich, wie oft ich Antworten lese, in denen steht: "Verwenden Sie einfach das [ yadde-ya ] -Framework." Das ist alles schön und gut, aber ich möchte genau wissen, wie ich das selbst rollen soll zuerst und dann kann ich wissen , welche Art von Rahmen tatsächlich der Verwendung für mich sein könnte. Danke, dass du es so klar formuliert hast.
kmote

28

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 wird IocContainer. Warum? Wie bereits erwähnt, sind dies Anti-Muster.

Making the StandardKernelüberall verfügbar

Der Schlüssel zu Ninjects Magie ist die StandardKernel-Instanz, die zur Verwendung der .Get<T>()-Methode benötigt wird.

Alternativ zu Sondergards IocContainerkönnen Sie das StandardKernelInnere der App-Klasse erstellen.

Entfernen Sie einfach StartUpUri aus Ihrer App.xaml

<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

Dies ist der CodeBehind der App in App.xaml.cs

public partial class App
{
    private IKernel _iocKernel;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get<MainWindow>();
        Current.MainWindow.Show();
    }
}

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

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

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.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

Wenn Sie eine lokale Instanz des Kernels benötigen, können Sie diese als Eigenschaft einfügen.

    [Inject]
    public IKernel Kernel { private get; set; }

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 .

Der empfohlene Ansatz für die Verwendung eines DI-Containers in einem Softwaresystem besteht darin, dass der Kompositionsstamm der Anwendung der einzige Ort ist, an dem der Container direkt berührt wird.

Wie die Ninject.Extensions.Factory verwendet werden soll , kann auch rot sein hier .


Netter Ansatz. Ich habe Ninject noch nie auf diesem Level erkundet, aber ich kann sehen, dass ich es verpasse :)
Sondergard

@son thx. Am Ende Ihrer Antwort haben Sie angegeben, wenn jemand einen besseren Weg hat, teilen Sie ihn bitte mit. Könnten Sie dies einen Link hinzufügen?
LuckyLikey

Wenn jemand daran interessiert ist, wie man das benutzt Ninject.Extensions.Factory, gib es hier in den Kommentaren an und ich werde weitere Informationen hinzufügen.
LuckyLikey

1
@LuckyLikey: Wie könnte ich einem Fenster-Datenkontext über XAML, das keinen parameterlosen Konstruktor hat, ein ViewModel hinzufügen? Mit der Lösung von sondergard mit dem ServiceLocator wäre diese Situation möglich.
Thomas Geulen

Sagen Sie mir also bitte, wie ich Dienste abrufen kann, die ich in angehängten Objekten benötige. Sie sind immer statisch, sowohl das Hintergrundfeld DependencyPropertyals auch die Methoden Get und Set.
springy76

12

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

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

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.


1
Wie übergeben Sie Konstruktorargumente an untergeordnete Ansichten? Ich habe diesen Ansatz ausprobiert, erhalte jedoch Ausnahmen in der übergeordneten Ansicht, die mir mitteilen, dass die untergeordnete Ansicht keinen standardmäßigen parameterlosen Konstruktor hat
Doctor Jones,

10

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 ...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);

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.

public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}

Der clevere Teil ist, dass der View Model Locator dann in app.xaml oder einem gleichwertigen Element als Datenquelle erstellt wird.

<local:ViewModelLocator x:key="Vml" />

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.


Sie sollten keinen GetInstanceoder resolveaußerhalb des Bootstraps der Anwendung haben. Das ist der Punkt von DI!
Soleil - Mathieu Prévot

Ich bin damit einverstanden, dass Sie den Eigenschaftswert während des Startvorgangs festlegen können, aber zu behaupten, dass die Verwendung der verzögerten Instanziierung gegen DI ist, ist falsch.
Kidshaw

@kishaw habe ich nicht.
Soleil - Mathieu Prévot

3

Canonic DryIoc Fall

Das Beantworten eines alten Beitrags, aber dies zu DryIoctun und das zu tun, was ich denke, ist eine gute Verwendung von DI und Schnittstellen (minimale Verwendung konkreter Klassen).

  1. Der Ausgangspunkt einer WPF-App ist App.xaml , und dort erklären wir, welche ursprüngliche Ansicht verwendet werden soll. Wir machen das mit Code dahinter anstelle des Standard-xaml:
  2. StartupUri="MainWindow.xaml"in App.xaml entfernen
  3. Fügen Sie in codebehind (App.xaml.cs) Folgendes hinzu override OnStartup:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DryContainer.Resolve<MainWindow>().Show();
    }

das ist der Startpunkt; das ist auch der einzige Ort, an dem resolveangerufen werden sollte.

  1. 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:

    public Container DryContainer { get; private set; }
    
    public App()
    {
        DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
        DryContainer.Register<IDatabaseManager, DatabaseManager>();
        DryContainer.Register<IJConfigReader, JConfigReader>();
        DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
            Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
        DryContainer.Register<MainWindow>();
    }

Anmerkungen und einige weitere Details

  • Ich habe konkrete Klasse nur mit der Ansicht verwendet MainWindow;
  • Ich musste angeben, welcher Konstruktor für das ViewModel verwendet werden soll (das müssen wir mit DryIoc tun), da der Standardkonstruktor für den XAML-Designer vorhanden sein muss und der Konstruktor mit Injektion der tatsächlich für die Anwendung verwendete ist.

Der ViewModel-Konstruktor mit DI:

public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
    _dbMgr = dbmgr;
    _jconfigReader = jconfigReader;
}

ViewModel-Standardkonstruktor für Design:

public MainWindowViewModel()
{
}

Der Code hinter der Ansicht:

public partial class MainWindow
{
    public MainWindow(IMainWindowViewModel vm)
    {
        InitializeComponent();
        ViewModel = vm;
    }

    public IViewModel ViewModel
    {
        get { return (IViewModel)DataContext; }
        set { DataContext = value; }
    }
}

und was in der Ansicht (MainWindow.xaml) benötigt wird, um eine Entwurfsinstanz mit ViewModel zu erhalten:

d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"

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.


2

Verwenden Sie das Managed Extensibility Framework .

[Export(typeof(IViewModel)]
public class SomeViewModel : IViewModel
{
    private IStorage _storage;

    [ImportingConstructor]
    public SomeViewModel(IStorage storage){
        _storage = storage;
    }

    public bool ProperlyInitialized { get { return _storage != null; } }
}

[Export(typeof(IStorage)]
public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

//Somewhere in your application bootstrapping...
public GetViewModel() {
     //Search all assemblies in the same directory where our dll/exe is
     string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
     var catalog = new DirectoryCatalog(currentPath);
     var container = new CompositionContainer(catalog);
     var viewModel = container.GetExport<IViewModel>();
     //Assert that MEF did as advertised
     Debug.Assert(viewModel is SomViewModel); 
     Debug.Assert(viewModel.ProperlyInitialized);
}

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‚s DataContextzu 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).


Sie vermissen einen wichtigen Punkt von DI, der darin besteht, jegliche Instanzerstellung mit zu vermeiden new.
Soleil - Mathieu Prévot

0

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:

public static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) =>{
    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if(context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
        viewTypeName = viewTypeName + "." + context;
    }

    var viewType = (from assmebly in AssemblySource.Instance
                    from type in assmebly.GetExportedTypes()
                    where type.FullName == viewTypeName
                    select type).FirstOrDefault();

    return viewType == null
        ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
        : GetOrCreateViewType(viewType);
};

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.


2
Ihr gesamter Beitrag ist Meinung.
John Peters

-1

Entfernen Sie die Start-URL aus Ihrer app.xaml.

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IoC.Configure(true);

        StartupUri = new Uri("Views/MainWindowView.xaml", UriKind.Relative);

        base.OnStartup(e);
    }
}

Jetzt können Sie Ihre IoC-Klasse verwenden, um die Instanzen zu erstellen.

MainWindowView.xaml.cs

public partial class MainWindowView
{
    public MainWindowView()
    {
        var mainWindowViewModel = IoC.GetInstance<IMainWindowViewModel>();

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

Sie sollten nicht alle Behälter haben GetInstancevon resolveauß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.
Soleil - Mathieu Prévot
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.