Jak obsługiwać iniekcję zależności w aplikacji WPF / MVVM

103

Rozpoczynam nową aplikację desktopową i chcę ją zbudować przy użyciu MVVM i WPF.

Zamierzam również skorzystać z TDD.

Problem polega na tym, że nie wiem, jak powinienem użyć kontenera IoC, aby wstrzyknąć moje zależności w moim kodzie produkcyjnym.

Załóżmy, że mam następującą klasę i interfejs:

public interface IStorage
{
    bool SaveFile(string content);
}

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

A potem mam inną klasę, która ma IStoragejako zależność, przypuśćmy również, że ta klasa jest ViewModel lub klasą biznesową ...

public class SomeViewModel
{
    private IStorage _storage;

    public SomeViewModel(IStorage storage){
        _storage = storage;
    }
}

Dzięki temu mogę łatwo pisać testy jednostkowe, aby upewnić się, że działają poprawnie, używając makiet itp.

Problem polega na tym, że chodzi o użycie go w prawdziwej aplikacji. Wiem, że muszę mieć kontener IoC, który łączy domyślną implementację IStorageinterfejsu, ale jak mam to zrobić?

Na przykład, jak by to było, gdybym miał następujący xaml:

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

Jak poprawnie „powiedzieć” WPF, aby w takim przypadku wstrzyknął zależności?

Załóżmy również, że potrzebuję wystąpienia SomeViewModelz mojego kodu C #, jak mam to zrobić?

Czuję, że jestem w tym całkowicie zagubiony, byłbym wdzięczny za każdy przykład lub wskazówkę, jak najlepiej sobie z tym poradzić.

Znam StructureMap, ale nie jestem ekspertem. Ponadto, jeśli istnieje lepszy / łatwiejszy / gotowy do użycia framework, daj mi znać.

Fedaykin
źródło
Z .net core 3.0 w wersji zapoznawczej można to zrobić z niektórymi pakietami Microsoft NuGet.
Bailey Miller,

Odpowiedzi:

88

Używałem Ninject i stwierdziłem, że praca z nim to przyjemność. Wszystko jest ustawione w kodzie, składnia jest dość prosta i ma dobrą dokumentację (i mnóstwo odpowiedzi na SO).

Zasadniczo wygląda to tak:

Utwórz model widoku i weź IStorageinterfejs jako parametr konstruktora:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Utwórz ViewModelLocatorz właściwością get dla modelu widoku, która wczytuje model widoku z Ninject:

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

Utwórz ViewModelLocatorzasób dla całej aplikacji w App.xaml:

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

Powiąż DataContextz UserControlodpowiednią właściwością w ViewModelLocator.

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

Utwórz klasę dziedziczącą NinjectModule, która skonfiguruje niezbędne powiązania ( IStoragei model widoku):

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

Zainicjuj jądro IoC podczas uruchamiania aplikacji za pomocą niezbędnych modułów Ninject (na razie tego powyżej):

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

        base.OnStartup(e);
    }
}

Użyłem IocKernelklasy statycznej do przechowywania instancji jądra IoC obejmującej całą aplikację, dzięki czemu mogę łatwo uzyskać do niego dostęp w razie potrzeby:

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);
        }
    }
}

To rozwiązanie wykorzystuje statyczny ServiceLocator(the IocKernel), który jest powszechnie uważany za anty-wzorzec, ponieważ ukrywa zależności klasy. Jednak bardzo trudno jest uniknąć pewnego rodzaju ręcznego wyszukiwania usług dla klas interfejsu użytkownika, ponieważ muszą one mieć konstruktor bez parametrów, a mimo to nie można kontrolować wystąpienia, więc nie można wstrzyknąć maszyny wirtualnej. Przynajmniej ten sposób pozwala przetestować maszynę wirtualną w izolacji, czyli tam, gdzie znajduje się cała logika biznesowa.

Jeśli ktoś ma lepszy sposób, podziel się nim.

EDYCJA: Lucky Likey dostarczył odpowiedź, aby pozbyć się statycznego lokalizatora usług, pozwalając Ninject na utworzenie instancji klas interfejsu użytkownika. Szczegóły odpowiedzi można zobaczyć tutaj

sondergard
źródło
13
Jestem nowy w wstrzykiwaniu zależności, ale w jego sercu Twoje rozwiązanie polega na połączeniu anty-wzorca Service Locator z Ninject, ponieważ używasz statycznego ViewModel Locator. Można argumentować, że wstrzyknięcie jest wykonywane w pliku Xaml, który jest mniej prawdopodobny do przetestowania. Nie mam lepszego rozwiązania i prawdopodobnie skorzystam z twojego - ale myślę, że warto byłoby wspomnieć o tym również w odpowiedzi.
user3141326
Człowiek rozwiązanie jest po prostu super, jest tylko jeden „problem” z następującą linię: DataContext="{Binding [...]}". Powoduje to, że VS-Designer wykonuje cały kod programu w konstruktorze ViewModel. W moim przypadku okno jest wykonywane i modalnie blokuje wszelkie interakcje z VS. Być może należy zmodyfikować ViewModelLocator, aby nie lokalizować „prawdziwych” modeli ViewModels w czasie projektowania. - Innym rozwiązaniem jest „Wyłącz kod projektu”, co również zapobiegnie wyświetlaniu wszystkiego innego. Może już znalazłeś zgrabne rozwiązanie tego problemu. W takim razie proszę o pokazanie tego.
LuckyLikey
@LuckyLikey Możesz spróbować użyć d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}", ale nie jestem pewien, czy to robi różnicę. Ale dlaczego / w jaki sposób konstruktor maszyny wirtualnej uruchamia okno modalne? A jakie okno?
sondergard
@son Właściwie nie wiem dlaczego i jak, ale kiedy otwieram projektanta okien z Eksploratora rozwiązań, w miarę otwierania nowej karty, projektant wyświetla okno i pojawia się to samo okno, jak przy debugowaniu modalnym, hostowane w nowym procesie poza VS „Micorosoft Visual Studio XAML Designer”. Jeśli proces zostanie zamknięty, również VS-Designer nie powiedzie się z powyższym wyjątkiem. Mam zamiar spróbować obejścia tego problemu. Powiadomię Cię, gdy
wykryję
1
@sondergard Opublikowałem poprawkę do Twojej odpowiedzi, unikając Anty-Patternu ServiceLocator. Zapraszam do sprawdzenia.
LuckyLikey
52

W swoim pytaniu ustawiasz wartość DataContextwłaściwości widoku w XAML. Wymaga to, aby model widoku miał domyślnego konstruktora. Jednak, jak już zauważyłeś, nie działa to dobrze w przypadku wstrzykiwania zależności, w którym chcesz wstrzyknąć zależności w konstruktorze.

Dlatego nie można ustawić DataContextwłaściwości w języku XAML . Zamiast tego masz inne alternatywy.

Jeśli aplikacja jest oparta na prostym hierarchicznym modelu widoku, możesz skonstruować całą hierarchię modelu widoku podczas uruchamiania aplikacji (będziesz musiał usunąć StartupUriwłaściwość z App.xamlpliku):

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();
  }

}

Opiera się to na wykresie obiektów modeli widoku zakorzenionych w programie, RootViewModelale można wstrzyknąć niektóre fabryki modeli widoku do nadrzędnych modeli widoków, umożliwiając im tworzenie nowych podrzędnych modeli widoków, dzięki czemu wykres obiektu nie musi być naprawiany. Mam nadzieję, że to również odpowiada na twoje pytanie, przypuszczam, że potrzebuję wystąpienia SomeViewModelz mojego cskodu, jak mam to zrobić?

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 */);
  }

}

Jeśli Twoja aplikacja ma bardziej dynamiczny charakter i być może opiera się na nawigacji, będziesz musiał podłączyć się do kodu, który wykonuje nawigację. Za każdym razem, gdy przechodzisz do nowego widoku, musisz utworzyć model widoku (z kontenera DI), sam widok i ustawić DataContextwidok na model widoku. Możesz najpierw wykonać ten widok, w którym wybierasz model widoku oparty na widoku, lub możesz to zrobić najpierw model widokugdzie model widoku określa, którego widoku użyć. Struktura MVVM zapewnia tę kluczową funkcjonalność z pewnym sposobem na podłączenie kontenera DI do tworzenia modeli widoków, ale można ją również zaimplementować samodzielnie. Jestem tutaj trochę niejasny, ponieważ w zależności od potrzeb ta funkcjonalność może stać się dość złożona. Jest to jedna z podstawowych funkcji, które uzyskujesz z frameworka MVVM, ale wprowadzenie własnej w prostej aplikacji da ci dobre zrozumienie, jakie frameworki MVVM zapewniają pod maską.

Brak możliwości zadeklarowania DataContextw XAML powoduje utratę obsługi w czasie projektowania. Jeśli model widoku zawiera jakieś dane, pojawią się one w czasie projektowania, co może być bardzo przydatne. Na szczęście atrybutów czasu projektowania można używać również w WPF. Jednym ze sposobów jest dodanie następujących atrybutów do <Window>elementu lub <UserControl>w języku XAML:

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}"

Typ modelu widoku powinien mieć dwa konstruktory, domyślny dla danych czasu projektowania i inny dla iniekcji zależności:

class MyViewModel : INotifyPropertyChanged {

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

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

}

W ten sposób możesz użyć iniekcji zależności i zachować dobrą obsługę w czasie projektowania.

Martin Liversage
źródło
13
To jest DOKŁADNIE to, czego szukałem. Frustruje mnie, ile razy czytam odpowiedzi, które mówią „Po prostu użyj schematu [ yadde-ya ]”. To wszystko dobrze i dobrze, ale najpierw chcę dokładnie wiedzieć, jak to zrobić, a potem mogę wiedzieć, jakiego rodzaju framework może mi się przydać. Dziękuję za tak jasne sformułowanie.
kmote
28

To, co tutaj zamieszczam, to ulepszenie odpowiedzi sondergarda, ponieważ to, co powiem, nie pasuje do komentarza :)

W rzeczywistości przedstawiam zgrabne rozwiązanie, które eliminuje potrzebę ServiceLocator i opakowania dla StandardKernel-Instance, które w rozwiązaniu sondergarda nazywa się IocContainer. Czemu? Jak wspomniano, są to anty-wzorce.

Udostępnianie StandardKernelwszędzie

Kluczem do magii Ninject jest StandardKernel-Instance, która jest potrzebna do użycia .Get<T>()-Method.

Alternatywnie do sondergardów IocContainermożesz stworzyć StandardKernelwewnętrzną Appklasę -Class.

Po prostu usuń StartUpUri z pliku 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>

To jest CodeBehind aplikacji w pliku 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();
    }
}

Od teraz Ninject żyje i jest gotowy do walki :)

Wstrzyknięcie DataContext

Ponieważ Ninject żyje, możesz wykonywać wszelkiego rodzaju iniekcje, np. Property Setter Injection lub najpopularniejszy Constructor Injection .

W ten sposób można wstrzykiwać ViewModel do swoich Window„sDataContext

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

Oczywiście możesz również wstrzyknąć, IViewModeljeśli wykonasz odpowiednie wiązania, ale to nie jest częścią tej odpowiedzi.

Bezpośredni dostęp do jądra

Jeśli potrzebujesz wywołać metody bezpośrednio w jądrze (np. .Get<T>()-Method), możesz pozwolić jądru wstrzyknąć sobie.

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

Jeśli potrzebujesz lokalnej instancji jądra, możesz wstrzyknąć ją jako Właściwość.

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

Chociaż może to być całkiem przydatne, nie polecałbym ci tego robić. Zwróć uwagę, że obiekty wstrzyknięte w ten sposób nie będą dostępne wewnątrz Konstruktora, ponieważ zostaną wprowadzone później.

Zgodnie z tym linkiem powinieneś użyć rozszerzenia fabrycznego zamiast wstrzykiwania IKernel(DI Container).

Zalecane podejście do stosowania kontenera DI w systemie oprogramowania polega na tym, że główny katalog kompozycji aplikacji to pojedyncze miejsce, w którym kontener jest dotykany bezpośrednio.

Sposób użycia Ninject.Extensions.Factory można również tutaj zaznaczyć na czerwono .

LuckyLikey
źródło
Niezłe podejście. Nigdy nie eksplorowałem Ninject do tego poziomu, ale widzę, że mi brakuje :)
sondergard
@son thx. Na końcu swojej odpowiedzi stwierdziłeś, że jeśli ktoś ma lepszy sposób, podziel się nim. Czy możesz dodać link to?
LuckyLikey
jeśli ktoś jest zainteresowany jak na tym skorzystać Ninject.Extensions.Factory, podaj to tutaj w komentarzach a dodam więcej informacji.
LuckyLikey
1
@LuckyLikey: Jak mogę dodać ViewModel do kontekstu danych okna za pośrednictwem XAML, który nie ma konstruktora bez parametrów? Dzięki rozwiązaniu firmy sondergard z ServiceLocator taka sytuacja byłaby możliwa.
Thomas Geulen
Więc proszę, powiedz mi, jak uzyskać usługi, których potrzebuję w dołączonych nieruchomościach? Są zawsze statyczne, zarówno DependencyPropertypole zapasowe, jak i jego metody Get i Set.
springy76
12

Wybieram podejście „najpierw widok”, gdzie przekazuję model widoku do konstruktora widoku (w jego kodzie), który zostaje przypisany do kontekstu danych, np.

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

        DataContext = viewModel;
    }
}

Zastępuje to podejście oparte na języku XAML.

Używam frameworka Prism do obsługi nawigacji - kiedy jakiś kod żąda wyświetlenia określonego widoku (przez „nawigację” do niego), Prism rozwiąże ten widok (wewnętrznie, używając frameworka DI aplikacji); struktura DI z kolei rozwiąże wszelkie zależności, które ma widok (model widoku w moim przykładzie), a następnie rozwiąże jego zależności i tak dalej.

Wybór frameworka DI jest prawie nieistotny, ponieważ wszystkie one zasadniczo robią to samo, tj. Rejestrujesz interfejs (lub typ) wraz z konkretnym typem, który ma utworzyć instancję framework, gdy znajdzie zależność od tego interfejsu. Dla porządku używam Castle Windsor.

Nawigacja w Pryzmacie wymaga trochę czasu, ale jest całkiem dobra, gdy już się nią zajmiesz, umożliwiając komponowanie aplikacji przy użyciu różnych widoków. Np. Możesz utworzyć „region” Prism w swoim głównym oknie, a następnie używając nawigacji Prism przełączać się z jednego widoku do innego w tym regionie, np. Gdy użytkownik wybiera pozycje menu lub cokolwiek innego.

Alternatywnie spójrz na jedną z platform MVVM, takich jak MVVM Light. Nie mam z nimi żadnego doświadczenia, więc nie mogę komentować tego, czego lubią używać.

Andrew Stephens
źródło
1
W jaki sposób przekazujesz argumenty konstruktora do widoków potomnych? Wypróbowałem to podejście, ale otrzymałem wyjątki w widoku rodzica, które mówią mi, że widok potomny nie ma domyślnego konstruktora bez parametrów
Doctor Jones,
10

Zainstaluj MVVM Light.

Część instalacji polega na utworzeniu lokalizatora modelu widoku. Jest to klasa, która ujawnia modele widoku jako właściwości. Funkcja pobierająca te właściwości może następnie zostać zwrócona z Twojego silnika IOC. Na szczęście MVVM light zawiera również framework SimpleIOC, ale jeśli chcesz, możesz podłączyć inne.

Dzięki prostemu IOC rejestrujesz implementację dla typu ...

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

W tym przykładzie model widoku jest tworzony i przekazywany obiekt dostawcy usług zgodnie z jego konstruktorem.

Następnie tworzysz właściwość, która zwraca instancję z IOC.

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

Sprytną częścią jest to, że lokalizator modelu widoku jest następnie tworzony w pliku app.xaml lub równoważnym jako źródło danych.

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

Możesz teraz powiązać z jego właściwością „MyViewModel”, aby pobrać Viewmodel z wstrzykniętą usługą.

Mam nadzieję, że to pomoże. Przepraszamy za nieścisłości kodu zakodowanego z pamięci na iPadzie.

kidshaw
źródło
Nie powinieneś mieć bootstrapu aplikacji GetInstanceani resolvepoza nim. O to chodzi w DI!
Soleil - Mathieu Prévot
Zgadzam się, że można ustawić wartość właściwości podczas uruchamiania, ale sugerowanie, że używanie leniwej instancji jest przeciwko DI jest błędne.
kidshaw
@kishaw Nie.
Soleil - Mathieu Prévot
3

Obudowa Canonic DryIoc

Odpowiadając na stary post, ale robiąc to z DryIoctym, co myślę, jest dobrym wykorzystaniem DI i interfejsów (minimalne użycie konkretnych klas).

  1. Punktem wyjścia aplikacji WPF jest App.xaml, i tam mówimy, jaki jest pierwotny widok do użycia; robimy to z kodem za zamiast domyślnego xaml:
  2. usunąć StartupUri="MainWindow.xaml"w App.xaml
  3. w codebehind (App.xaml.cs) dodaj to override OnStartup:

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

to jest punkt startowy; to także jedyne miejsce, do którego resolvenależy dzwonić.

  1. korzeń konfiguracji (zgodnie z książką Marka Seemana Dependency injection in .NET; jedyne miejsce, w którym należy wspomnieć o konkretnych klasach) będzie w tym samym kodzie, w konstruktorze:

    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>();
    }

Uwagi i kilka dodatkowych szczegółów

  • Użyłem konkretnej klasy tylko z widokiem MainWindow;
  • Musiałem określić, którego konstruktora użyć (musimy to zrobić z DryIoc) dla ViewModel, ponieważ domyślny konstruktor musi istnieć dla projektanta XAML, a konstruktor z iniekcją jest rzeczywistym używanym dla aplikacji.

Konstruktor ViewModel z DI:

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

Domyślny konstruktor ViewModel do projektowania:

public MainWindowViewModel()
{
}

Kod za widokiem:

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

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

i co jest potrzebne w widoku (MainWindow.xaml), aby uzyskać wystąpienie projektu z ViewModel:

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

Wniosek

W ten sposób otrzymaliśmy bardzo czystą i minimalną implementację aplikacji WPF z kontenerem DryIoc i DI, zachowując jednocześnie możliwe wystąpienia projektowe widoków i modeli widoków.

Soleil - Mathieu Prévot
źródło
2

Użyj zarządzanej struktury rozszerzalności .

[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);
}

Ogólnie rzecz biorąc, należy mieć klasę statyczną i użyć wzorca fabryki, aby zapewnić globalny kontener (buforowany, natch).

Jeśli chodzi o wstrzykiwanie modeli widoku, wstrzykujesz je w ten sam sposób, w jaki wstrzykujesz wszystko inne. Utwórz konstruktora importującego (lub umieść instrukcję importu we właściwości / polu) w kodzie źródłowym pliku XAML i powiedz mu, aby zaimportował model widoku. Następnie powiązać Window„s DataContextdo tej nieruchomości. Twoje obiekty główne, które faktycznie wyciągasz z pojemnika, są zwykle Windowobiektami złożonymi . Po prostu dodaj interfejsy do klas okien i wyeksportuj je, a następnie pobierz z katalogu jak powyżej (w App.xaml.cs ... to jest plik bootstrap WPF).

Sprytny neologizm
źródło
Brakuje Ci jednego ważnego punktu DI, którym jest uniknięcie tworzenia instancji z new.
Soleil - Mathieu Prévot
0

Sugerowałbym użycie ViewModel - pierwsze podejście https://github.com/Caliburn-Micro/Caliburn.Micro

zobacz: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions

używać Castle Windsorjako kontenera IOC.

Wszystko o konwencjach

Jedną z głównych cech Caliburn.Micro jest jego zdolność do wyeliminowania konieczności stosowania kodu płyty kotłowej poprzez działanie na szeregu konwencji. Niektórzy kochają konwencje, a inni ich nienawidzą. Dlatego konwencje CM są w pełni konfigurowalne i można je nawet całkowicie wyłączyć, jeśli nie jest to pożądane. Jeśli zamierzasz używać konwencji, a ponieważ są one domyślnie WŁĄCZONE, dobrze jest wiedzieć, jakie są te konwencje i jak działają. To jest temat tego artykułu. Wyświetl rozdzielczość (najpierw model widoku)

Podstawy

Pierwsza konwencja, z którą możesz się spotkać podczas korzystania z CM, dotyczy rozdzielczości widoku. Ta konwencja ma wpływ na wszystkie obszary ViewModel-First w aplikacji. W ViewModel-First mamy istniejący ViewModel, który musimy wyrenderować na ekranie. Aby to zrobić, CM używa prostego wzorca nazewnictwa, aby znaleźć UserControl1, który powinien powiązać z ViewModel i wyświetlić. Więc co to za wzór? Wystarczy spojrzeć na ViewLocator.LocateForModelType, aby dowiedzieć się:

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);
};

Najpierw zignorujmy zmienną „kontekst”. Aby uzyskać widok, zakładamy, że używasz tekstu „ViewModel” w nazewnictwie maszyn wirtualnych, więc po prostu zmieniamy go na „View” wszędzie tam, gdzie go znajdziemy, usuwając słowo „Model”. Ma to wpływ na zmianę zarówno nazw typów, jak i przestrzeni nazw. Więc ViewModels.CustomerViewModel stałoby się Views.CustomerView. Lub jeśli organizujesz swoją aplikację według funkcji: CustomerManagement.CustomerViewModel staje się CustomerManagement.CustomerView. Miejmy nadzieję, że to całkiem proste. Gdy już mamy nazwę, szukamy typów o tej nazwie. Przeszukujemy dowolny zestaw, który udostępnisz CM jako możliwy do przeszukiwania za pośrednictwem AssemblySource.Instance.2 Jeśli znajdziemy typ, tworzymy wystąpienie (lub pobieramy je z kontenera IoC, jeśli jest zarejestrowany) i zwracamy je do obiektu wywołującego. Jeśli nie znajdziemy typu,

Wróćmy teraz do wartości „kontekstu”. W ten sposób CM obsługuje wiele widoków w tym samym modelu widoku. Jeśli podano kontekst (zazwyczaj ciąg znaków lub wyliczenie), dokonujemy dalszej transformacji nazwy na podstawie tej wartości. Ta transformacja skutecznie zakłada, że ​​masz folder (przestrzeń nazw) dla różnych widoków, usuwając słowo „Widok” z końca i dołączając zamiast tego kontekst. Tak więc, biorąc pod uwagę kontekst „Master”, nasz ViewModels.CustomerViewModel stałby się Views.Customer.Master.

Nahum
źródło
2
Cały Twój post to opinia.
John Peters,
-1

Usuń startowy identyfikator URI z pliku 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);
    }
}

Teraz możesz użyć swojej klasy IoC do konstruowania instancji.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}
C Bauer
źródło
Nie powinieneś mieć żadnego kontenera GetInstancez resolvezewnętrznym plikiem app.xaml.cs, tracisz punkt DI. Ponadto wspominanie o widoku XAML w za kodem widoku jest trochę zawiłe. Po prostu wywołaj widok w czystym języku C # i zrób to z kontenerem.
Soleil - Mathieu Prévot