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 IStorage
ketergantungan, 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 IStorage
antarmuka, 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 SomeViewModel
kode 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.
sumber
Jawaban:
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
IStorage
antarmuka sebagai parameter konstruktor:Buat
ViewModelLocator
dengan properti get untuk model tampilan, yang memuat model tampilan dari Ninject:Jadikan
ViewModelLocator
aplikasi sebagai sumber daya yang luas di App.xaml:Ikat
DataContext
dariUserControl
ke properti terkait di ViewModelLocator.Buat kelas yang mewarisi NinjectModule, yang akan menyiapkan binding yang diperlukan (
IStorage
dan viewmodel):Inisialisasi kernel IoC saat memulai aplikasi dengan modul Ninject yang diperlukan (yang di atas untuk saat ini):
Saya telah menggunakan
IocKernel
kelas statis untuk menampung contoh luas aplikasi dari kernel IoC, jadi saya dapat dengan mudah mengaksesnya saat diperlukan:Solusi ini menggunakan statik
ServiceLocator
(theIocKernel
), 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
sumber
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.Dalam pertanyaan Anda, Anda menetapkan nilai
DataContext
properti 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
DataContext
properti 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
StartupUri
properti dariApp.xaml
file):Ini didasarkan pada grafik objek dari model tampilan yang di-root pada
RootViewModel
tetapi 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 kodeSomeViewModel
sayacs
, bagaimana saya harus melakukannya?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
DataContext
tampilan 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
DataContext
di 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:Jenis model tampilan harus memiliki dua konstruktor, default untuk data waktu desain dan satu lagi untuk injeksi ketergantungan:
Dengan melakukan ini, Anda dapat menggunakan injeksi ketergantungan dan mempertahankan dukungan waktu desain yang baik.
sumber
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 disebutIocContainer
. Mengapa? Seperti yang disebutkan, itu adalah anti pola.Membuatnya
StandardKernel
tersedia di mana sajaKunci sihir Ninject adalah
StandardKernel
-Instance yang diperlukan untuk menggunakan.Get<T>()
-Method.Sebagai alternatif untuk sondergard,
IocContainer
Anda dapat membuat bagianStandardKernel
dalamApp
-Class.Hapus saja StartUpUri dari App.xaml Anda
Ini adalah CodeBehind Aplikasi di dalam App.xaml.cs
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
Window
milik AndaDataContext
Tentu saja Anda juga dapat menyuntikkan
IViewModel
jika 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.Jika Anda memerlukan instance lokal dari Kernel, Anda dapat memasukkannya sebagai Properti.
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).Bagaimana Ninject.Extensions.Factory akan digunakan juga bisa berwarna merah di sini .
sumber
Ninject.Extensions.Factory
ini, sebutkan di sini di komentar dan saya akan menambahkan beberapa informasi lebih lanjut.DependencyProperty
field maupun metode Get dan Set-nya.Saya menggunakan pendekatan "lihat dulu", di mana saya meneruskan model tampilan ke konstruktor tampilan (dalam kode di belakangnya), yang ditugaskan ke konteks data, misalnya
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.
sumber
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 ...
Dalam contoh ini, model tampilan Anda dibuat dan diteruskan objek penyedia layanan sesuai konstruktornya.
Anda kemudian membuat properti yang mengembalikan instance dari IOC.
Bagian cerdasnya adalah bahwa pencari model tampilan kemudian dibuat di app.xaml atau yang setara sebagai sumber data.
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.
sumber
GetInstance
atau diresolve
luar bootstrap aplikasi. Itulah inti dari DI!Kasus Canonic DryIoc
Menjawab posting lama, tetapi melakukan ini dengan
DryIoc
dan melakukan apa yang menurut saya adalah penggunaan DI dan antarmuka yang baik (penggunaan minimal kelas beton).App.xaml
, dan di sana kami memberi tahu tampilan awal apa yang akan digunakan; kami melakukannya dengan kode di belakang alih-alih xaml default:StartupUri="MainWindow.xaml"
di App.xamldalam codebehind (App.xaml.cs) tambahkan ini
override OnStartup
:itulah titik awal; itu juga satu-satunya tempat yang
resolve
harus dihubungi.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:
Keterangan dan detail lainnya
MainWindow
;Konstruktor ViewModel dengan DI:
Konstruktor default ViewModel untuk desain:
Kode di belakang tampilan:
dan apa yang dibutuhkan dalam tampilan (MainWindow.xaml) untuk mendapatkan contoh desain dengan ViewModel:
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.
sumber
Gunakan Kerangka Kerja Ekstensi yang Dikelola .
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
'sDataContext
untuk properti itu. Objek root yang Anda keluarkan sendiri dari wadah biasanya merupakanWindow
objek tersusun . Cukup tambahkan antarmuka ke kelas jendela, dan ekspor, lalu ambil dari katalog seperti di atas (di App.xaml.cs ... itu file bootstrap WPF).sumber
new
.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 Windsor
sebagai 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:
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.
sumber
Hapus uri startup dari app.xaml Anda.
App.xaml.cs
Sekarang Anda dapat menggunakan kelas IoC Anda untuk membuat instance.
MainWindowView.xaml.cs
sumber
GetInstance
diresolve
luar 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.