Injeksi dependensi
.NET Multi-platform App UI (.NET MAUI) menyediakan dukungan bawaan untuk menggunakan injeksi dependensi. Injeksi dependensi adalah versi khusus dari pola Inversion of Control (IoC), di mana kekhawatiran yang terbalik adalah proses mendapatkan dependensi yang diperlukan. Dengan injeksi dependensi, kelas lain bertanggung jawab untuk menyuntikkan dependensi ke dalam objek saat runtime.
Biasanya, konstruktor kelas dipanggil saat membuat instans objek, dan nilai apa pun yang dibutuhkan objek diteruskan sebagai argumen ke konstruktor. Ini adalah contoh injeksi dependensi yang dikenal sebagai injeksi konstruktor. Dependensi yang dibutuhkan objek disuntikkan ke dalam konstruktor.
Catatan
Ada juga jenis injeksi dependensi lainnya, seperti injeksi setter properti dan injeksi panggilan metode, tetapi kurang umum digunakan.
Dengan menentukan dependensi sebagai jenis antarmuka, injeksi dependensi memungkinkan pemisahan jenis beton dari kode yang bergantung pada jenis ini. Ini umumnya menggunakan kontainer yang menyimpan daftar pendaftaran dan pemetaan antara antarmuka dan jenis abstrak, dan jenis beton yang mengimplementasikan atau memperluas jenis ini.
Kontainer injeksi dependensi
Jika kelas tidak secara langsung membuat instans objek yang dibutuhkannya, kelas lain harus mengambil tanggung jawab ini. Pertimbangkan contoh berikut, yang menunjukkan kelas model tampilan yang memerlukan argumen konstruktor:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
Dalam contoh ini, MainPageViewModel
konstruktor memerlukan dua instans objek antarmuka sebagai argumen yang disuntikkan oleh kelas lain. Satu-satunya MainPageViewModel
dependensi di kelas adalah pada jenis antarmuka. Oleh karena itu, MainPageViewModel
kelas tidak memiliki pengetahuan tentang kelas yang bertanggung jawab untuk membuat instans objek antarmuka.
Demikian pula, pertimbangkan contoh berikut yang memperlihatkan kelas halaman yang memerlukan argumen konstruktor:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
Dalam contoh ini, MainPage
konstruktor memerlukan jenis konkret sebagai argumen yang disuntikkan oleh kelas lain. Satu-satunya MainPage
dependensi di kelas ada pada jenisnya MainPageViewModel
. Oleh karena itu, MainPage
kelas tidak memiliki pengetahuan tentang kelas yang bertanggung jawab untuk membuat instans jenis beton.
Dalam kedua kasus, kelas yang bertanggung jawab untuk membuat instans dependensi, dan memasukkannya ke dalam kelas dependen, dikenal sebagai kontainer injeksi dependensi.
Kontainer injeksi dependensi mengurangi konektor antar objek dengan menyediakan fasilitas untuk membuat instans kelas dan mengelola masa pakainya berdasarkan konfigurasi kontainer. Selama pembuatan objek, kontainer menyuntikkan dependensi apa pun yang diperlukan objek. Jika dependensi tersebut belum dibuat, kontainer akan membuat dan menyelesaikan dependensinya terlebih dahulu.
Ada beberapa keuntungan menggunakan kontainer injeksi dependensi:
- Kontainer menghilangkan kebutuhan kelas untuk menemukan dependensinya dan mengelola masa pakainya.
- Kontainer memungkinkan pemetaan dependensi yang diimplementasikan tanpa memengaruhi kelas.
- Kontainer memfasilitasi uji coba dengan memungkinkan dependensi ditiru.
- Kontainer meningkatkan keberlanjutan dengan memungkinkan kelas baru ditambahkan dengan mudah ke aplikasi.
Dalam konteks aplikasi .NET MAUI yang menggunakan pola Model-View-ViewModel (MVVM), kontainer injeksi dependensi biasanya akan digunakan untuk mendaftarkan dan menyelesaikan tampilan, mendaftarkan dan menyelesaikan model tampilan, dan untuk mendaftarkan layanan dan menyuntikkannya ke dalam model tampilan. Untuk informasi selengkapnya tentang pola MVVM, lihat Model-View-ViewModel (MVVM).
Ada banyak kontainer injeksi dependensi yang tersedia untuk .NET. .NET MAUI memiliki dukungan bawaan untuk digunakan Microsoft.Extensions.DependencyInjection untuk mengelola instansiasi tampilan, melihat model, dan kelas layanan dalam aplikasi. Microsoft.Extensions.DependencyInjection memfasilitasi pembuatan aplikasi yang digabungkan secara longgar, dan menyediakan semua fitur yang umum ditemukan dalam kontainer injeksi dependensi, termasuk metode untuk mendaftarkan pemetaan jenis dan instans objek, menyelesaikan objek, mengelola masa pakai objek, dan menyuntikkan objek dependen ke dalam konstruktor objek yang diselesaikannya. Untuk informasi selengkapnya tentang Microsoft.Extensions.DependencyInjection, lihat Injeksi dependensi di .NET.
Pada runtime, kontainer harus mengetahui implementasi dependensi mana yang diminta untuk membuat instans objek yang diminta. Dalam contoh di atas, ILoggingService
antarmuka dan ISettingsService
perlu diselesaikan sebelum MainPageViewModel
objek dapat dibuat. Ini melibatkan kontainer yang melakukan tindakan berikut:
- Memutuskan cara membuat instans objek yang mengimplementasikan antarmuka. Ini dikenal sebagai pendaftaran. Untuk informasi selengkapnya, lihat Pendaftaran.
- Membuat instans objek yang mengimplementasikan antarmuka dan objek yang
MainPageViewModel
diperlukan. Ini dikenal sebagai resolusi. Untuk informasi selengkapnya, lihat Resolusi.
Akhirnya, aplikasi akan selesai menggunakan MainPageViewModel
objek, dan akan tersedia untuk pengumpulan sampah. Pada titik ini, pengumpul sampah harus membuang implementasi antarmuka berumur pendek jika kelas lain tidak berbagi instans yang sama.
Pendaftaran
Sebelum dependensi dapat disuntikkan ke dalam objek, jenis untuk dependensi harus terlebih dahulu didaftarkan ke kontainer. Mendaftarkan jenis biasanya melibatkan melewati kontainer jenis beton, atau antarmuka dan jenis beton yang mengimplementasikan antarmuka.
Ada dua pendekatan utama untuk mendaftarkan jenis dan objek dengan kontainer:
- Daftarkan jenis atau pemetaan dengan kontainer. Ini dikenal sebagai pendaftaran sementara. Jika diperlukan, kontainer akan membangun instans dari jenis yang ditentukan.
- Daftarkan objek yang ada dalam kontainer sebagai singleton. Jika diperlukan, kontainer akan mengembalikan referensi ke objek yang ada.
Perhatian
Kontainer injeksi dependensi tidak selalu cocok untuk aplikasi .NET MAUI. Injeksi dependensi memperkenalkan kompleksitas dan persyaratan tambahan yang mungkin tidak sesuai atau berguna untuk aplikasi yang lebih kecil. Jika kelas tidak memiliki dependensi apa pun, atau bukan dependensi untuk jenis lain, mungkin tidak masuk akal untuk memasukkannya ke dalam kontainer. Selain itu, jika kelas memiliki satu set dependensi yang terintegrasi dengan jenis dan tidak akan pernah berubah, mungkin tidak masuk akal untuk memasukkannya ke dalam kontainer.
Pendaftaran jenis yang memerlukan injeksi dependensi harus dilakukan dalam satu metode di aplikasi Anda. Metode ini harus dipanggil lebih awal dalam siklus hidup aplikasi untuk memastikannya mengetahui dependensi antara kelasnya. Aplikasi biasanya harus melakukan ini dalam CreateMauiApp
metode di MauiProgram
kelas . Kelas MauiProgram
memanggil ke dalam CreateMauiApp
metode untuk membuat MauiAppBuilder objek. Objek MauiAppBuilder memiliki Services properti jenis IServiceCollection, yang menyediakan tempat untuk mendaftarkan jenis Anda, seperti tampilan, model tampilan, dan layanan untuk injeksi dependensi:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddTransient<ILoggingService, LoggingService>();
builder.Services.AddTransient<ISettingsService, SettingsService>();
builder.Services.AddSingleton<MainPageViewModel>();
builder.Services.AddSingleton<MainPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
Jenis yang terdaftar dengan Services properti disediakan untuk kontainer injeksi dependensi saat MauiAppBuilder.Build() dipanggil.
Saat mendaftarkan dependensi, Anda perlu mendaftarkan semua dependensi termasuk jenis apa pun yang memerlukan dependensi. Oleh karena itu, jika Anda memiliki model tampilan yang mengambil dependensi sebagai parameter konstruktor, Anda perlu mendaftarkan model tampilan bersama dengan semua dependensinya. Demikian pula, jika Anda memiliki tampilan yang mengambil dependensi model tampilan sebagai parameter konstruktor, Anda perlu mendaftarkan tampilan, dan model tampilan bersama dengan semua dependensinya.
Tip
Kontainer injeksi dependensi sangat ideal untuk membuat instans model tampilan. Jika model tampilan memiliki dependensi, model tampilan akan mengelola pembuatan dan injeksi layanan yang diperlukan. Pastikan Anda mendaftarkan model tampilan dan dependensi apa pun yang mungkin mereka miliki dalam CreateMauiApp
metode di MauiProgram
kelas .
Masa pakai dependensi
Bergantung pada kebutuhan aplikasi, Anda mungkin perlu mendaftarkan dependensi dengan masa pakai yang berbeda. Tabel berikut mencantumkan metode utama yang dapat Anda gunakan untuk mendaftarkan dependensi, dan masa pakai pendaftarannya:
Metode | Deskripsi |
---|---|
AddSingleton<T> |
Membuat satu instans objek yang akan tetap ada selama masa pakai aplikasi. |
AddTransient<T> |
Membuat instans baru objek saat diminta selama resolusi. Objek sementara tidak memiliki masa pakai yang telah ditentukan sebelumnya, tetapi biasanya akan mengikuti masa pakai host mereka. |
AddScoped<T> |
Membuat instans objek yang berbagi masa pakai hostnya. Ketika host keluar dari cakupan, begitu juga dependensinya. Oleh karena itu, menyelesaikan dependensi yang sama beberapa kali dalam cakupan yang sama menghasilkan instans yang sama, sambil menyelesaikan dependensi yang sama dalam cakupan yang berbeda akan menghasilkan instans yang berbeda. |
Catatan
Jika objek tidak mewarisi dari antarmuka, seperti tampilan atau model tampilan, hanya jenis konkretnya yang perlu disediakan untuk AddSingleton<T>
metode , , AddTransient<T>
atau AddScoped<T>
.
Kelas MainPageViewModel
ini digunakan di dekat akar aplikasi dan harus selalu tersedia, jadi mendaftarkannya AddSingleton<T>
bermanfaat. Model tampilan lainnya dapat dinavigasi secara situasional atau digunakan nanti di aplikasi. Jika Anda memiliki jenis yang mungkin tidak selalu digunakan, atau jika itu memori atau intensif komputasi atau memerlukan data just-in-time, itu mungkin kandidat yang lebih baik untuk AddTransient<T>
pendaftaran.
Cara umum lain untuk mendaftarkan dependensi adalah menggunakan AddSingleton<TService, TImplementation>
metode , AddTransient<TService, TImplementation>
, atau AddScoped<TService, TImplementation>
. Metode ini mengambil dua jenis - definisi antarmuka dan implementasi konkret. Jenis pendaftaran ini paling baik untuk kasus di mana Anda menerapkan layanan berdasarkan antarmuka.
Setelah semua jenis terdaftar, MauiAppBuilder.Build() harus dipanggil untuk membuat MauiApp objek dan mengisi kontainer injeksi dependensi dengan semua jenis terdaftar.
Penting
Setelah MauiAppBuilder.Build() dipanggil, jenis yang terdaftar dengan kontainer injeksi dependensi akan tidak dapat diubah dan tidak lagi dapat diperbarui atau dimodifikasi.
Mendaftarkan dependensi dengan metode ekstensi
Metode ini MauiApp.CreateBuilder membuat MauiAppBuilder objek yang dapat digunakan untuk mendaftarkan dependensi. Jika aplikasi Anda perlu mendaftarkan banyak dependensi, Anda dapat membuat metode ekstensi untuk membantu menyediakan alur kerja pendaftaran yang terorganisir dan dapat dipertahankan:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<App>()
.RegisterServices()
.RegisterViewModels()
.RegisterViews()
.Build();
public static MauiAppBuilder RegisterServices(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddTransient<ILoggingService, LoggingService>();
mauiAppBuilder.Services.AddTransient<ISettingsService, SettingsService>();
// More services registered here.
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<MainPageViewModel>();
// More view-models registered here.
return mauiAppBuilder;
}
public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<MainPage>();
// More views registered here.
return mauiAppBuilder;
}
}
Dalam contoh ini, tiga metode ekstensi pendaftaran menggunakan MauiAppBuilder instans untuk mengakses Services properti untuk mendaftarkan dependensi.
Resolusi
Setelah jenis terdaftar, jenis dapat diselesaikan atau disuntikkan sebagai dependensi. Ketika jenis sedang diselesaikan, dan kontainer perlu membuat instans baru, kontainer tersebut menyuntikkan dependensi apa pun ke dalam instans.
Umumnya, ketika jenis diselesaikan, salah satu dari tiga skenario terjadi:
- Jika jenis belum terdaftar, kontainer akan melemparkan pengecualian.
- Jika jenis telah didaftarkan sebagai singleton, kontainer mengembalikan instans singleton. Jika ini adalah pertama kalinya jenis dipanggil, kontainer membuatnya jika diperlukan dan mempertahankan referensi ke dalamnya.
- Jika jenis telah didaftarkan sebagai sementara, kontainer mengembalikan instans baru dan tidak mempertahankan referensi ke dalamnya.
.NET MAUI mendukung resolusi dependensi otomatis dan eksplisit . Resolusi dependensi otomatis menggunakan injeksi konstruktor tanpa meminta dependensi secara eksplisit dari kontainer. Resolusi dependensi eksplisit terjadi sesuai permintaan dengan secara eksplisit meminta dependensi dari kontainer.
Resolusi dependensi otomatis
Resolusi dependensi otomatis terjadi di aplikasi yang menggunakan .NET MAUI Shell, asalkan Anda telah mendaftarkan jenis dependensi dan jenis yang menggunakan dependensi dengan kontainer injeksi dependensi.
Selama navigasi berbasis Shell, .NET MAUI akan mencari pendaftaran halaman, dan jika ada yang ditemukan, itu akan membuat halaman tersebut dan menyuntikkan dependensi apa pun ke dalam konstruktornya:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
Dalam contoh ini, MainPage
konstruktor menerima instans yang disuntikkan MainPageViewModel
. Pada gilirannya MainPageViewModel
, instans memiliki ILoggingService
instans dan ISettingsService
disuntikkan:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
Selain itu, dalam aplikasi berbasis Shell, .NET MAUI akan menyuntikkan dependensi ke halaman detail yang terdaftar dengan metode .Routing.RegisterRoute
Resolusi dependensi eksplisit
Aplikasi berbasis Shell tidak dapat menggunakan injeksi konstruktor saat jenis hanya mengekspos konstruktor tanpa parameter. Atau, jika aplikasi Anda tidak menggunakan Shell, Anda harus menggunakan resolusi dependensi eksplisit.
Kontainer injeksi dependensi dapat diakses secara eksplisit dari Element melalui propertinya Handler.MauiContext.Service
, yang berjenis IServiceProvider:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
HandlerChanged += OnHandlerChanged;
}
void OnHandlerChanged(object sender, EventArgs e)
{
BindingContext = Handler.MauiContext.Services.GetService<MainPageViewModel>();
}
}
Pendekatan ini dapat berguna jika Anda perlu menyelesaikan dependensi dari Element, atau dari luar konstruktor Element. Dalam contoh ini, mengakses kontainer injeksi dependensi di HandlerChanged
penanganan aktivitas memastikan bahwa handler telah diatur untuk halaman, dan oleh karena itu properti Handler
tidak akan menjadi null
.
Peringatan
Properti Handler
Anda Element
bisa , null
jadi ketahuilah bahwa Anda mungkin perlu memperhitungkan situasi ini. Untuk informasi selengkapnya, lihat Siklus hidup handler.
Dalam model tampilan, kontainer injeksi dependensi dapat diakses secara eksplisit melalui Handler.MauiContext.Service
properti :Application.Current.MainPage
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel()
{
_loggingService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ILoggingService>();
_settingsService = Application.Current.MainPage.Handler.MauiContext.Services.GetService<ISettingsService>();
}
}
Kelemahan dari pendekatan ini adalah bahwa model tampilan sekarang memiliki dependensi pada jenisnya Application . Namun, kelemahan ini dapat dihilangkan dengan meneruskan IServiceProvider argumen ke konstruktor model tampilan. IServiceProvider diselesaikan melalui resolusi dependensi otomatis tanpa harus mendaftarkannya dengan kontainer injeksi dependensi. Dengan pendekatan ini, jenis dan dependensinya IServiceProvider dapat diselesaikan secara otomatis asalkan jenis terdaftar dengan kontainer injeksi dependensi. IServiceProvider kemudian dapat digunakan untuk resolusi dependensi eksplisit:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(IServiceProvider serviceProvider)
{
_loggingService = serviceProvider.GetService<ILoggingService>();
_settingsService = serviceProvider.GetService<ISettingsService>();
}
}
Selain itu, IServiceProvider instans dapat diakses di setiap platform melalui IPlatformApplication.Current.Services
properti .
Batasan dengan sumber daya XAML
Skenario umum adalah mendaftarkan halaman dengan kontainer injeksi dependensi, dan menggunakan resolusi dependensi otomatis untuk menyuntikkannya ke App
konstruktor dan mengaturnya sebagai nilai MainPage
properti:
public App(MyFirstAppPage page)
{
InitializeComponent();
MainPage = page;
}
Namun, dalam skenario ini jika MyFirstAppPage
upaya untuk mengakses StaticResource
yang telah dideklarasikan dalam XAML dalam App
kamus sumber daya, XamlParseException akan dilemparkan dengan pesan yang mirip Position {row}:{column}. StaticResource not found for key {key}
dengan . Ini terjadi karena halaman yang diselesaikan melalui injeksi konstruktor telah dibuat sebelum sumber daya XAML tingkat aplikasi telah diinisialisasi.
Solusi untuk masalah ini adalah menyuntikkan IServiceProvider ke kelas Anda App
lalu menggunakannya untuk menyelesaikan halaman di dalam App
kelas:
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
MainPage = serviceProvider.GetService<MyFirstAppPage>();
}
Pendekatan ini memaksa pohon objek XAML untuk dibuat dan diinisialisasi sebelum halaman diselesaikan.