Cara menangani injeksi ketergantungan di aplikasi WPF / MVVM

103

Saya memulai aplikasi desktop baru dan saya ingin membuatnya menggunakan MVVM dan WPF.

Saya juga berniat menggunakan TDD.

Masalahnya adalah saya tidak tahu bagaimana saya harus menggunakan wadah IoC untuk menyuntikkan ketergantungan saya pada kode produksi saya.

Misalkan saya memiliki kelas dan antarmuka berikut:

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

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

Dan kemudian saya memiliki kelas lain yang memiliki IStorageketergantungan, misalkan juga bahwa kelas ini adalah ViewModel atau kelas bisnis ...

public class SomeViewModel
{
    private IStorage _storage;

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

Dengan ini saya dapat dengan mudah menulis unit test untuk memastikan bahwa mereka bekerja dengan baik, menggunakan tiruan dan lain-lain.

Masalahnya adalah ketika menggunakannya dalam aplikasi nyata. Saya tahu bahwa saya harus memiliki wadah IoC yang menautkan implementasi default untuk IStorageantarmuka, tetapi bagaimana saya melakukannya?

Misalnya, bagaimana jadinya jika saya memiliki xaml berikut:

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

Bagaimana saya bisa dengan benar 'memberitahu' WPF untuk menyuntikkan dependensi dalam kasus itu?

Juga, misalkan saya memerlukan contoh dari SomeViewModelkode C # saya, bagaimana saya harus melakukannya?

Saya merasa benar-benar tersesat dalam hal ini, saya sangat menghargai setiap contoh atau panduan tentang bagaimana cara terbaik untuk menanganinya.

Saya akrab dengan StructureMap, tetapi saya bukan seorang ahli. Selain itu, jika ada kerangka kerja yang lebih baik / lebih mudah / out-of-the-box, beri tahu saya.

Fedaykin
sumber
Dengan .net core 3.0 dalam pratinjau, Anda dapat melakukannya dengan beberapa paket nuget Microsoft.
Bailey Miller

Jawaban:

88

Saya telah menggunakan Ninject, dan merasa senang bekerja dengannya. Semuanya sudah diatur dalam kode, sintaksnya cukup mudah dan memiliki dokumentasi yang baik (dan banyak jawaban tentang SO).

Jadi pada dasarnya seperti ini:

Buat model tampilan, dan gunakan IStorageantarmuka sebagai parameter konstruktor:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Buat ViewModelLocatordengan properti get untuk model tampilan, yang memuat model tampilan dari Ninject:

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

Jadikan ViewModelLocatoraplikasi sebagai sumber daya yang luas di App.xaml:

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

Ikat DataContextdari UserControlke properti terkait di ViewModelLocator.

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

Buat kelas yang mewarisi NinjectModule, yang akan menyiapkan binding yang diperlukan ( IStoragedan viewmodel):

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

Inisialisasi kernel IoC saat memulai aplikasi dengan modul Ninject yang diperlukan (yang di atas untuk saat ini):

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

        base.OnStartup(e);
    }
}

Saya telah menggunakan IocKernelkelas statis untuk menampung contoh luas aplikasi dari kernel IoC, jadi saya dapat dengan mudah mengaksesnya saat diperlukan:

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

Solusi ini menggunakan statik ServiceLocator(the IocKernel), yang umumnya dianggap sebagai anti-pola, karena menyembunyikan dependensi kelas. Namun, sangat sulit untuk menghindari semacam pencarian layanan manual untuk kelas UI, karena mereka harus memiliki konstruktor tanpa parameter, dan Anda tetap tidak dapat mengontrol pembuatan instance, sehingga Anda tidak dapat memasukkan VM. Setidaknya dengan cara ini memungkinkan Anda untuk menguji VM secara terpisah, di situlah semua logika bisnis berada.

Jika ada yang punya cara yang lebih baik, silakan berbagi.

EDIT: Lucky Likey memberikan jawaban untuk menyingkirkan pencari layanan statis, dengan membiarkan Ninject membuat instance kelas UI. Detail jawabannya bisa dilihat disini

sondergard.dll
sumber
13
Saya baru mengenal injeksi ketergantungan, namun di dalam hatinya solusi Anda menggabungkan pola anti-Service Locator dengan Ninject karena Anda menggunakan ViewModel Locator statis. Orang dapat berargumen bahwa injeksi dilakukan dalam file Xaml, yang kemungkinannya kecil untuk diuji. Saya tidak memiliki solusi yang lebih baik dan mungkin akan menggunakan solusi Anda - namun menurut saya akan membantu untuk menyebutkan ini dalam jawaban juga.
user3141326
Man solusi Anda hanya besar, hanya ada satu "Masalah" dengan Jalur berikut: DataContext="{Binding [...]}". Hal ini menyebabkan VS-Designer untuk mengeksekusi semua Kode-Program di Konstruktor ViewModel. Dalam kasus saya, Window sedang dijalankan dan secara sederhana memblokir setiap interaksi ke VS. Mungkin seseorang harus memodifikasi ViewModelLocator agar tidak menemukan ViewModels "sebenarnya" di Design-Time. - Solusi lain adalah "Nonaktifkan Kode Proyek", yang juga akan mencegah semua hal lain ditampilkan. Mungkin Anda sudah menemukan solusi yang tepat untuk ini. Dalam hal ini, saya harap Anda menunjukkannya.
LuckyLikey
@LuckyLikey Anda dapat mencoba menggunakan d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" tapi saya tidak yakin itu membuat perbedaan. Tetapi mengapa / bagaimana konstruktor VM meluncurkan jendela modal? Dan jendela macam apa?
sondergard
@son Sebenarnya saya tidak tahu mengapa dan bagaimana, tetapi ketika saya membuka Window Designer dari Solution Explorer, saat Tab baru dibuka, jendela ditampilkan oleh desainer dan jendela yang sama muncul seolah-olah debugging modal, dihosting dalam proses baru di luar VS "Micorosoft Visual Studio XAML Designer". Jika proses dimatikan, VS-Designer juga gagal dengan pengecualian yang disebutkan sebelumnya. Saya akan mencoba solusi Anda. Saya akan memberi tahu Anda saat saya mendeteksi Info baru :)
LuckyLikey
1
@sondergard Saya telah memposting perbaikan pada jawaban Anda, menghindari ServiceLocator Anti-Pattern. Jangan ragu untuk memeriksanya.
LuckyLikey
52

Dalam pertanyaan Anda, Anda menetapkan nilai DataContextproperti tampilan di XAML. Ini mengharuskan model tampilan Anda memiliki konstruktor default. Namun, seperti yang telah Anda catat, ini tidak berfungsi dengan baik dengan injeksi dependensi di mana Anda ingin memasukkan dependensi di konstruktor.

Jadi, Anda tidak dapat mengatur DataContextproperti di XAML . Sebaliknya, Anda memiliki alternatif lain.

Jika aplikasi Anda didasarkan pada model tampilan hierarki sederhana, Anda dapat membuat seluruh hierarki model tampilan saat aplikasi dimulai (Anda harus menghapus StartupUriproperti dari App.xamlfile):

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

}

Ini didasarkan pada grafik objek dari model tampilan yang di-root pada RootViewModeltetapi Anda dapat memasukkan beberapa pabrik model tampilan ke dalam model tampilan induk yang memungkinkan mereka membuat model tampilan anak baru sehingga grafik objek tidak harus diperbaiki. Ini juga mudah-mudahan menjawab pertanyaan Anda, misalkan saya memerlukan contoh dari kode SomeViewModelsaya cs, bagaimana saya harus melakukannya?

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

}

Jika aplikasi Anda lebih dinamis dan mungkin didasarkan pada navigasi, Anda harus menghubungkan ke kode yang melakukan navigasi. Setiap kali Anda menavigasi ke tampilan baru, Anda perlu membuat model tampilan (dari wadah DI), tampilan itu sendiri dan menyetel DataContexttampilan ke model tampilan. Anda dapat melakukan tampilan ini terlebih dahulu di mana Anda memilih model tampilan berdasarkan tampilan atau Anda dapat melakukannya model tampilan terlebih dahuludi mana model tampilan menentukan tampilan mana yang akan digunakan. Kerangka kerja MVVM menyediakan fungsionalitas kunci ini dengan beberapa cara bagi Anda untuk mengaitkan kontainer DI Anda ke dalam pembuatan model tampilan tetapi Anda juga dapat mengimplementasikannya sendiri. Saya agak tidak jelas di sini karena tergantung pada kebutuhan Anda, fungsi ini mungkin menjadi sangat kompleks. Ini adalah salah satu fungsi inti yang Anda dapatkan dari kerangka kerja MVVM, tetapi menjalankan fungsi Anda sendiri dalam aplikasi sederhana akan memberi Anda pemahaman yang baik tentang apa yang disediakan kerangka kerja MVVM.

Dengan tidak dapat mendeklarasikan DataContextdi XAML Anda kehilangan beberapa dukungan waktu desain. Jika model tampilan Anda berisi beberapa data, itu akan muncul selama waktu desain yang bisa sangat berguna. Untungnya, Anda juga dapat menggunakan atribut waktu desain di WPF. Salah satu cara untuk melakukannya adalah dengan menambahkan atribut berikut ke <Window>elemen atau <UserControl>di 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}"

Jenis model tampilan harus memiliki dua konstruktor, default untuk data waktu desain dan satu lagi untuk injeksi ketergantungan:

class MyViewModel : INotifyPropertyChanged {

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

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

}

Dengan melakukan ini, Anda dapat menggunakan injeksi ketergantungan dan mempertahankan dukungan waktu desain yang baik.

Martin Liversage
sumber
13
Inilah PERSIS apa yang saya cari. Saya frustrasi berapa kali saya membaca jawaban yang mengatakan "Cukup gunakan kerangka kerja [ yadde-ya ]." Itu semua bagus dan bagus, tapi saya ingin tahu persis bagaimana menggulung ini sendiri terlebih dahulu dan kemudian saya bisa tahu kerangka seperti apa yang mungkin benar-benar berguna bagi saya. Terima kasih telah mengejanya dengan sangat jelas.
kmote
28

Yang saya posting di sini adalah perbaikan dari Jawaban sondergard, karena yang akan saya sampaikan tidak cocok dengan Komentar :)

Dalam Fakta saya memperkenalkan solusi yang rapi, yang menghindari kebutuhan dari ServiceLocator dan pembungkus untuk StandardKernel-contoh, yang pada sondergard ini Solusi disebut IocContainer. Mengapa? Seperti yang disebutkan, itu adalah anti pola.

Membuatnya StandardKerneltersedia di mana saja

Kunci sihir Ninject adalah StandardKernel-Instance yang diperlukan untuk menggunakan .Get<T>()-Method.

Sebagai alternatif untuk sondergard, IocContainerAnda dapat membuat bagian StandardKerneldalam App-Class.

Hapus saja StartUpUri dari App.xaml Anda

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

Ini adalah CodeBehind Aplikasi di dalam 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();
    }
}

Mulai sekarang, Ninject masih hidup dan siap bertarung :)

Menyuntikkan file DataContext

Karena Ninject masih hidup, Anda dapat melakukan semua jenis injeksi, misalnya Injeksi Penyetel Properti atau Injeksi Konstruktor yang paling umum .

Ini adalah cara Anda memasukkan ViewModel ke dalam Windowmilik AndaDataContext

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

Tentu saja Anda juga dapat menyuntikkan IViewModeljika Anda melakukan binding yang benar, tetapi itu bukan bagian dari jawaban ini.

Mengakses Kernel secara langsung

Jika Anda perlu memanggil Metode pada Kernel secara langsung (mis. .Get<T>()-Method), Anda dapat membiarkan Kernel menginjeksi dirinya sendiri.

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

Jika Anda memerlukan instance lokal dari Kernel, Anda dapat memasukkannya sebagai Properti.

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

Meskipun ini bisa sangat berguna, saya tidak akan merekomendasikan Anda untuk melakukannya. Perhatikan saja bahwa objek yang diinjeksi dengan cara ini, tidak akan tersedia di dalam Constructor, karena disuntikkan nanti.

Menurut tautan ini Anda harus menggunakan Ekstensi-pabrik daripada menyuntikkan IKernel(Kontainer DI).

Pendekatan yang direkomendasikan untuk menggunakan wadah DI dalam sistem perangkat lunak adalah bahwa Akar Komposisi aplikasi menjadi satu tempat di mana wadah disentuh secara langsung.

Bagaimana Ninject.Extensions.Factory akan digunakan juga bisa berwarna merah di sini .

LuckyLikey
sumber
Pendekatan yang bagus. Tidak pernah menjelajahi Ninject ke level ini, tetapi saya dapat melihat bahwa saya ketinggalan :)
sondergard
@son th. Di akhir Jawaban Anda, Anda menyatakan Jika ada yang memiliki cara yang lebih baik, silakan berbagi. Bisakah Anda menambahkan tautan ini?
LuckyLikey
jika seseorang tertarik tentang cara menggunakan Ninject.Extensions.Factoryini, sebutkan di sini di komentar dan saya akan menambahkan beberapa informasi lebih lanjut.
LuckyLikey
1
@ LuckyLikey: Bagaimana saya bisa menambahkan ViewModel ke jendela konteks data melalui XAML yang tidak memiliki konstruktor tanpa parameter? Dengan solusi dari sondergard dengan ServiceLocator situasi ini mungkin terjadi.
Thomas Geulen
Jadi tolong beri tahu saya cara mendapatkan kembali layanan yang saya perlukan di properti terlampir? Mereka selalu statis, baik backing DependencyPropertyfield maupun metode Get dan Set-nya.
springy76
12

Saya menggunakan pendekatan "lihat dulu", di mana saya meneruskan model tampilan ke konstruktor tampilan (dalam kode di belakangnya), yang ditugaskan ke konteks data, misalnya

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

        DataContext = viewModel;
    }
}

Ini menggantikan pendekatan berbasis XAML Anda.

Saya menggunakan kerangka Prism untuk menangani navigasi - ketika beberapa kode meminta tampilan tertentu ditampilkan (dengan "menavigasi" ke sana), Prism akan menyelesaikan tampilan itu (secara internal, menggunakan kerangka DI aplikasi); kerangka DI pada gilirannya akan menyelesaikan dependensi bahwa pandangan memiliki (model tampilan dalam contoh saya), kemudian memutuskan nya dependensi, dan sebagainya.

Pilihan kerangka DI cukup banyak tidak relevan karena mereka semua pada dasarnya melakukan hal yang sama, yaitu Anda mendaftarkan sebuah antarmuka (atau tipe) bersama dengan tipe konkret yang Anda inginkan untuk dipakai kerangka ketika menemukan ketergantungan pada antarmuka itu. Sebagai catatan saya menggunakan Castle Windsor.

Navigasi prisma membutuhkan waktu untuk membiasakan diri tetapi cukup bagus setelah Anda memahaminya, memungkinkan Anda untuk membuat aplikasi menggunakan tampilan yang berbeda. Misalnya, Anda dapat membuat "wilayah" Prisma di jendela utama Anda, kemudian menggunakan navigasi Prisma Anda akan beralih dari satu tampilan ke tampilan lain dalam wilayah ini, misalnya saat pengguna memilih item menu atau apa pun.

Atau lihat salah satu kerangka kerja MVVM seperti MVVM Light. Saya tidak punya pengalaman tentang ini jadi tidak bisa berkomentar tentang apa yang mereka suka gunakan.

Andrew Stephens
sumber
1
Bagaimana Anda meneruskan argumen konstruktor ke tampilan anak? Saya telah mencoba pendekatan ini, tetapi mendapatkan pengecualian dalam tampilan induk yang memberi tahu saya bahwa tampilan anak tidak memiliki konstruktor tanpa parameter default
Doctor Jones
10

Pasang MVVM Light.

Bagian dari penginstalan adalah membuat pencari model tampilan. Ini adalah kelas yang mengekspos viewmodels Anda sebagai properti. Pengambil properti ini kemudian dapat mengembalikan instance dari mesin IOC Anda. Untungnya, lampu MVVM juga menyertakan kerangka SimpleIOC, tetapi Anda dapat memasang yang lain jika Anda mau.

Dengan IOC sederhana Anda mendaftarkan implementasi terhadap tipe ...

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

Dalam contoh ini, model tampilan Anda dibuat dan diteruskan objek penyedia layanan sesuai konstruktornya.

Anda kemudian membuat properti yang mengembalikan instance dari IOC.

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

Bagian cerdasnya adalah bahwa pencari model tampilan kemudian dibuat di app.xaml atau yang setara sebagai sumber data.

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

Anda sekarang dapat mengikat ke properti 'MyViewModel' untuk mendapatkan viewmodel Anda dengan layanan yang dimasukkan.

Semoga membantu. Permintaan maaf atas ketidakakuratan kode, yang dikodekan dari memori di iPad.

kidshaw
sumber
Anda tidak boleh memiliki GetInstanceatau di resolveluar bootstrap aplikasi. Itulah inti dari DI!
Soleil - Mathieu Prévot
Saya setuju bahwa Anda dapat menyetel nilai properti selama startup, tetapi menyarankan bahwa menggunakan lazy instantiation bertentangan dengan DI adalah salah.
kidshaw
@ Isyana saya tidak.
Soleil - Mathieu Prévot
3

Kasus Canonic DryIoc

Menjawab posting lama, tetapi melakukan ini dengan DryIocdan melakukan apa yang menurut saya adalah penggunaan DI dan antarmuka yang baik (penggunaan minimal kelas beton).

  1. Titik awal dari aplikasi WPF adalah App.xaml, dan di sana kami memberi tahu tampilan awal apa yang akan digunakan; kami melakukannya dengan kode di belakang alih-alih xaml default:
  2. hapus StartupUri="MainWindow.xaml"di App.xaml
  3. dalam codebehind (App.xaml.cs) tambahkan ini override OnStartup:

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

itulah titik awal; itu juga satu-satunya tempat yang resolveharus dihubungi.

  1. akar konfigurasi (menurut injeksi Ketergantungan buku Mark Seeman di .NET; satu-satunya tempat di mana kelas beton harus disebutkan) akan berada di belakang kode yang sama, di konstruktor:

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

Keterangan dan detail lainnya

  • Saya menggunakan kelas beton hanya dengan tampilan MainWindow;
  • Saya harus menentukan kontraktor mana yang akan digunakan (kita perlu melakukannya dengan DryIoc) untuk ViewModel, karena konstruktor default harus ada untuk desainer XAML, dan konstruktor dengan injeksi adalah yang sebenarnya digunakan untuk aplikasi.

Konstruktor ViewModel dengan DI:

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

Konstruktor default ViewModel untuk desain:

public MainWindowViewModel()
{
}

Kode di belakang tampilan:

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

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

dan apa yang dibutuhkan dalam tampilan (MainWindow.xaml) untuk mendapatkan contoh desain dengan ViewModel:

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

Kesimpulan

Oleh karena itu, kami mendapatkan implementasi yang sangat bersih dan minimal dari aplikasi WPF dengan wadah DryIoc dan DI sambil menjaga agar contoh desain tampilan dan model tampilan tetap memungkinkan.

Soleil - Mathieu Prévot
sumber
2

Gunakan Kerangka Kerja Ekstensi yang Dikelola .

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

Secara umum, apa yang akan Anda lakukan adalah memiliki kelas statis dan menggunakan Pola Pabrik untuk memberi Anda wadah global (cache, natch).

Adapun cara menyuntikkan model tampilan, Anda menyuntikkannya dengan cara yang sama Anda menyuntikkan yang lainnya. Buat konstruktor pengimporan (atau letakkan pernyataan impor pada properti / bidang) di belakang kode file XAML, dan beri tahu untuk mengimpor model tampilan. Kemudian mengikat Anda Window's DataContextuntuk properti itu. Objek root yang Anda keluarkan sendiri dari wadah biasanya merupakan Windowobjek tersusun . Cukup tambahkan antarmuka ke kelas jendela, dan ekspor, lalu ambil dari katalog seperti di atas (di App.xaml.cs ... itu file bootstrap WPF).

Neologisme Cerdas
sumber
Anda kehilangan satu poin penting dari DI yang menghindari pembuatan instance dengan new.
Soleil - Mathieu Prévot
0

Saya akan menyarankan untuk menggunakan ViewModel - Pendekatan pertama https://github.com/Caliburn-Micro/Caliburn.Micro

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

digunakan Castle Windsorsebagai wadah IOC.

Semua Tentang Konvensi

Salah satu fitur utama Caliburn.Micro terwujud dalam kemampuannya menghilangkan kebutuhan kode pelat boiler dengan bertindak berdasarkan serangkaian konvensi. Beberapa orang menyukai konvensi dan beberapa membencinya. Itulah mengapa konvensi CM dapat disesuaikan sepenuhnya dan bahkan dapat dimatikan sepenuhnya jika tidak diinginkan. Jika Anda akan menggunakan konvensi, dan karena konvensi tersebut AKTIF secara default, sebaiknya Anda mengetahui apa saja konvensi itu dan bagaimana cara kerjanya. Itulah pokok bahasan artikel ini. Lihat Resolusi (ViewModel-First)

Dasar

Konvensi pertama yang mungkin Anda temui saat menggunakan CM terkait dengan resolusi tampilan. Konvensi ini memengaruhi area ViewModel-First apa pun pada aplikasi Anda. Di ViewModel-First, kita memiliki ViewModel yang sudah ada yang perlu kita render ke layar. Untuk melakukan ini, CM menggunakan pola penamaan sederhana untuk menemukan UserControl1 yang harus diikat ke ViewModel dan tampilan. Jadi, pola apa itu? Mari kita lihat ViewLocator.LocateForModelType untuk mencari tahu:

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

Mari kita abaikan variabel "konteks" pada awalnya. Untuk mendapatkan tampilan, kami membuat asumsi bahwa Anda menggunakan teks "ViewModel" dalam penamaan VM Anda, jadi kami hanya mengubahnya menjadi "Tampilan" di mana pun yang kami temukan dengan menghapus kata "Model". Ini memiliki efek mengubah nama tipe dan namespace. Jadi ViewModels.CustomerViewModel akan menjadi Views.CustomerView. Atau jika Anda mengatur aplikasi Anda berdasarkan fitur: CustomerManagement.CustomerViewModel menjadi CustomerManagement.CustomerView. Mudah-mudahan, itu cukup mudah. Setelah kami memiliki namanya, kami kemudian mencari jenis dengan nama itu. Kami mencari perakitan apa pun yang Anda temukan CM sebagai dapat dicari melalui AssemblySource.Instance.2 Jika kami menemukan jenisnya, kami membuat instance (atau mendapatkannya dari wadah IoC jika terdaftar) dan mengembalikannya ke pemanggil. Jika kita tidak menemukan tipenya,

Sekarang, kembali ke nilai "konteks" itu. Ini adalah cara CM mendukung beberapa Tampilan melalui ViewModel yang sama. Jika konteks (biasanya string atau enum) disediakan, kami melakukan transformasi lebih lanjut dari nama tersebut, berdasarkan nilai itu. Transformasi ini secara efektif mengasumsikan Anda memiliki folder (namespace) untuk tampilan yang berbeda dengan menghapus kata "View" dari akhir dan menambahkan konteks sebagai gantinya. Jadi, dengan konteks "Master", ViewModels.CustomerViewModel kami akan menjadi Views.Customer.Master.

Nahum
sumber
2
Seluruh posting Anda adalah opini.
John Peters
-1

Hapus uri startup dari app.xaml Anda.

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

Sekarang Anda dapat menggunakan kelas IoC Anda untuk membuat instance.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}
C Bauer
sumber
Anda seharusnya tidak memiliki wadah GetInstancedi resolveluar app.xaml.cs, Anda kehilangan poin DI. Selain itu, menyebutkan tampilan xaml di belakang kode tampilan agak berbelit-belit. Panggil saja tampilan dalam c # murni, dan lakukan ini dengan wadahnya.
Soleil - Mathieu Prévot