Berbagi teknologi

WPF/C#: Cara mengimplementasikan injeksi ketergantungan di WPF

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Kata pengantar

Artikel ini menggunakan proyek Galeri WPF untuk mempelajari konsep terkait injeksi ketergantungan dan cara melakukan injeksi ketergantungan di WPF.

Apa itu injeksi ketergantungan

Dependency Injection (DI) adalah pola desain yang digunakan untuk mengimplementasikan prinsip Inversion of Control (IoC). Tujuan utama dari injeksi ketergantungan adalah untuk mentransfer pembuatan objek dan pengelolaan ketergantungan antar objek dari dalam objek ke wadah atau kerangka kerja eksternal, sehingga meningkatkan kemampuan pemeliharaan, kemampuan pengujian, dan fleksibilitas kode.

Konsep inti injeksi ketergantungan

  1. mengandalkan : Suatu objek memerlukan objek lain untuk menyelesaikan pekerjaannya, maka objek pertama bergantung pada objek lain. Misalnya, kelas OrderService mungkin mengandalkan kelas ProductRepository untuk memperoleh informasi produk.
  2. injeksi : Meneruskan objek dependen ke objek yang memerlukannya, alih-alih membiarkan objek yang memerlukannya membuat objek dependen itu sendiri. Injeksi dapat dicapai melalui konstruktor, properti, atau parameter metode.
  3. wadah : Kerangka kerja atau pustaka yang mengelola pembuatan dan ketergantungan objek. Kontainer bertanggung jawab untuk membuat instance objek, menyelesaikan dependensi, dan memasukkan objek dependen ke dalam objek yang memerlukannya.

Jenis injeksi ketergantungan

injeksi konstruktor: Objek dependen dilewatkan melalui konstruktor kelas.

public class OrderService
{
    private readonly IProductRepository _productRepository;

    public OrderService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Injeksi properti: Objek yang bergantung dilewatkan melalui properti publik kelas.

public class OrderService
{
    public IProductRepository ProductRepository { get; set; }
}
  • 1
  • 2
  • 3
  • 4

metode injeksi: Objek dependen dilewatkan melalui parameter metode kelas.

public class OrderService
{
    public void ProcessOrder(IProductRepository productRepository)
    {
        // 使用 productRepository 处理订单
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Mengapa injeksi ketergantungan diperlukan

Dependency Injection (DI) adalah pola desain di mana pembuatan objek dan pengelolaan ketergantungan antar objek dapat ditransfer dari dalam objek ke wadah atau kerangka eksternal. Ada beberapa alasan penting dan keuntungan injeksi ketergantungan:

  1. Kurangi kopling : Injeksi ketergantungan mentransfer pengelolaan dependensi dari dalam objek ke wadah eksternal, sehingga objek tidak perlu mengetahui cara membuat objek yang bergantung padanya, tetapi hanya perlu mengetahui antarmuka dari objek yang bergantung. Hal ini dapat secara signifikan mengurangi penggabungan antar objek dan membuat kode lebih modular dan fleksibel.
  2. Meningkatkan kemampuan pengujian : Injeksi ketergantungan membuat pengujian unit lebih mudah dan efisien. Dengan menggunakan objek tiruan atau stub alih-alih objek dependen sebenarnya, pengembang dapat melakukan pengujian unit tanpa bergantung pada implementasi sebenarnya. Hal ini membantu memastikan independensi dan keandalan tes.
  3. Meningkatkan pemeliharaan : Karena injeksi ketergantungan mengurangi sambungan antar objek, kode menjadi lebih modular dan jelas. Hal ini membuat kode lebih mudah untuk dipahami dan dipelihara. Saat Anda perlu memodifikasi atau mengganti objek dependen, Anda hanya perlu mengubah informasi konfigurasi atau registrasi, tanpa mengubah kode yang menggunakan objek tersebut.
  4. Meningkatkan fleksibilitas : Injeksi ketergantungan membuat sistem lebih fleksibel dan dapat dengan mudah mengganti objek yang bergantung untuk mencapai fungsi atau perilaku yang berbeda. Misalnya, implementasi lapisan akses database yang berbeda dapat dialihkan melalui file atau kode konfigurasi tanpa mengubah kode logika bisnis.
  5. Promosikan pemisahan kekhawatiran : Injeksi ketergantungan membantu mencapai pemisahan perhatian (Separation of Concerns), sehingga setiap objek hanya perlu fokus pada tanggung jawabnya sendiri, tanpa mempedulikan cara membuat dan memperoleh objek tempat bergantungnya. Ini membantu meningkatkan kejelasan dan pemeliharaan kode.
  6. Mendukung pola desain dan praktik terbaik : Injeksi ketergantungan adalah dasar dari banyak pola desain dan praktik terbaik, seperti Inversion of Control (IoC), Service Locator Pattern, dll. Dengan menggunakan injeksi ketergantungan, pengembang dapat lebih mudah menerapkan pola dan praktik ini, sehingga meningkatkan kualitas dan skalabilitas kode.

Bagaimana menerapkan injeksi ketergantungan

Artikel ini menggunakan proyek Galeri WPF untuk mempelajari cara menggunakan injeksi ketergantungan di alamat Kode WPF:

https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery

Untuk mengimplementasikan injeksi ketergantungan dalam proyek ini, dua paket berikut digunakan:

gambar-20240711100435001

Pertama lihat isi App.xaml.cs:

public partial class App : Application
{

    private static readonly IHost _host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
            services.AddSingleton<INavigationService, NavigationService>();
            services.AddSingleton<MainWindow>();
            services.AddSingleton<MainWindowViewModel>();
            
            services.AddTransient<DashboardPage>();
            services.AddTransient<DashboardPageViewModel>();

            services.AddTransient<ButtonPage>();
            services.AddTransient<ButtonPageViewModel>();
            services.AddTransient<CheckBoxPage>();
            services.AddTransient<CheckBoxPageViewModel>();
            services.AddTransient<ComboBoxPage>();
            services.AddTransient<ComboBoxPageViewModel>();
            services.AddTransient<RadioButtonPage>();
            services.AddTransient<RadioButtonPageViewModel>();
            services.AddTransient<SliderPage>();
            services.AddTransient<SliderPageViewModel>();
            services.AddTransient<CalendarPage>();
            services.AddTransient<CalendarPageViewModel>();
            services.AddTransient<DatePickerPage>();
            services.AddTransient<DatePickerPageViewModel>();
            services.AddTransient<TabControlPage>();
            services.AddTransient<TabControlPageViewModel>();
            services.AddTransient<ProgressBarPage>();
            services.AddTransient<ProgressBarPageViewModel>();
            services.AddTransient<MenuPage>();
            services.AddTransient<MenuPageViewModel>();
            services.AddTransient<ToolTipPage>();
            services.AddTransient<ToolTipPageViewModel>();
            services.AddTransient<CanvasPage>();
            services.AddTransient<CanvasPageViewModel>();
            services.AddTransient<ExpanderPage>();
            services.AddTransient<ExpanderPageViewModel>();
            services.AddTransient<ImagePage>();
            services.AddTransient<ImagePageViewModel>();
            services.AddTransient<DataGridPage>();
            services.AddTransient<DataGridPageViewModel>();
            services.AddTransient<ListBoxPage>();
            services.AddTransient<ListBoxPageViewModel>();
            services.AddTransient<ListViewPage>();
            services.AddTransient<ListViewPageViewModel>();
            services.AddTransient<TreeViewPage>();
            services.AddTransient<TreeViewPageViewModel>();
            services.AddTransient<LabelPage>();
            services.AddTransient<LabelPageViewModel>();
            services.AddTransient<TextBoxPage>();
            services.AddTransient<TextBoxPageViewModel>();
            services.AddTransient<TextBlockPage>();
            services.AddTransient<TextBlockPageViewModel>();
            services.AddTransient<RichTextEditPage>();
            services.AddTransient<RichTextEditPageViewModel>();
            services.AddTransient<PasswordBoxPage>();
            services.AddTransient<PasswordBoxPageViewModel>();
            services.AddTransient<ColorsPage>();
            services.AddTransient<ColorsPageViewModel>();

            services.AddTransient<LayoutPage>();
            services.AddTransient<LayoutPageViewModel>();
            services.AddTransient<AllSamplesPage>();
            services.AddTransient<AllSamplesPageViewModel>();
            services.AddTransient<BasicInputPage>();
            services.AddTransient<BasicInputPageViewModel>();
            services.AddTransient<CollectionsPage>();
            services.AddTransient<CollectionsPageViewModel>();
            services.AddTransient<MediaPage>();
            services.AddTransient<MediaPageViewModel>();
            services.AddTransient<NavigationPage>();
            services.AddTransient<NavigationPageViewModel>();
            services.AddTransient<TextPage>();
            services.AddTransient<TextPageViewModel>();
            services.AddTransient<DateAndTimePage>();
            services.AddTransient<DateAndTimePageViewModel>();
            services.AddTransient<StatusAndInfoPage>();
            services.AddTransient<StatusAndInfoPageViewModel>();
            services.AddTransient<SamplesPage>();
            services.AddTransient<SamplesPageViewModel>();
            services.AddTransient<DesignGuidancePage>();
            services.AddTransient<DesignGuidancePageViewModel>();

            services.AddTransient<UserDashboardPage>();
            services.AddTransient<UserDashboardPageViewModel>();

            services.AddTransient<TypographyPage>();
            services.AddTransient<TypographyPageViewModel>();

            services.AddSingleton<IconsPage>();
            services.AddSingleton<IconsPageViewModel>();

            services.AddSingleton<SettingsPage>();
            services.AddSingleton<SettingsPageViewModel>();

            services.AddSingleton<AboutPage>();
            services.AddSingleton<AboutPageViewModel>();
        }).Build();


    [STAThread]
    public static void Main()
    {
        _host.Start();

        App app = new();
        app.InitializeComponent();
        app.MainWindow = _host.Services.GetRequiredService<MainWindow>();
        app.MainWindow.Visibility = Visibility.Visible;
        app.Run();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

gambar-20240711083011393

Apa itu IHost?

Di C#,IHost adalah antarmuka yang digunakan di .NET untuk membangun dan mengkonfigurasi aplikasiHostkonsep abstrak.IHost Antarmuka mendefinisikan kumpulan layanan dan komponen yang diperlukan untuk memulai, menjalankan, dan mengelola aplikasi. Biasanya digunakan dengan aplikasi ASP.NET Core, tetapi juga berfungsi dengan jenis aplikasi .NET lainnya, seperti aplikasi konsol atau program WPF.

gambar-20240711082817268

IHostAntarmuka terdiri dariHostBuilderImplementasi kelas, yang menyediakan pembuatan dan konfigurasiIHostmetode contoh.HostBuilderMemungkinkan Anda menambahkan berbagai layanan seperti logging, konfigurasi, kontainer injeksi ketergantungan, dll., serta mengonfigurasi perilaku startup dan penghentian aplikasi Anda.

gambar-20240711083048854

gambar-20240711083156306

Memberikan metode kemudahan untuk membuat instans Microsoft.Extensions.Hosting.IHostBuilder dengan default yang telah dikonfigurasi sebelumnya.

gambar-20240711083713204

Mengembalikan IHostBuilder.

gambar-20240711084145756

gambar-20240711084211035

Tambahkan layanan ke wadah. Operasi ini dapat dipanggil beberapa kali dan hasilnya bersifat kumulatif.

Arti dari parameterconfigDelegate adalah untuk mengkonfigurasi delegasi Microsoft.Extensions.DependencyInjection.IServiceCollection.
Koleksi ini akan digunakan untuk membangun System.IServiceProvider.

Delegasi memerlukan dua tipe parameter: HostBuilderContext dan IServiceCollection, yang tidak memiliki nilai kembalian.

gambar-20240711084853971

Ekspresi Lambda yang memenuhi tipe delegasi diteruskan di sini.

Di C#,() => {} Merupakan sintaksis untuk ekspresi Lambda. Ekspresi Lambda adalah pembungkus delegasi ringan yang memungkinkan Anda menentukan metode anonim dan meneruskannya sebagai parameter ke metode yang mendukung delegasi atau pohon ekspresi.

Ekspresi Lambda menyediakan cara ringkas untuk mendefinisikan metode, dan berguna terutama ketika Anda perlu meneruskan metode sebagai parameter ke metode lain.

gambar-20240711085344696

Saat menambahkan layanan, ada dua siklus hidup di sini, selain AddSingleton dan AddTransient, ada juga AddScoped.

Metode ini menentukan siklus hidup layanan, yaitu bagaimana instans layanan dibuat dan dikelola dalam aplikasi.

TambahkanSingleton

  • lingkaran kehidupan:Lajang
  • arti : Hanya satu contoh layanan yang dibuat selama seluruh siklus hidup aplikasi. Tidak peduli berapa kali Anda memintanya dari penampung, instance yang sama akan dikembalikan.
  • Adegan yang berlaku: Cocok untuk layanan tanpa kewarganegaraan, atau sumber daya yang digunakan bersama di seluruh aplikasi, seperti konfigurasi, logger, dll.

Tambahkan Sementara

  • lingkaran kehidupan: Sementara
  • arti: Setiap kali layanan diminta dari kontainer, sebuah instance baru dibuat.
  • Adegan yang berlaku: Cocok untuk layanan stateful, atau skenario yang memerlukan instance baru untuk setiap permintaan, seperti halaman, model tampilan, dll.

Tambahkan Cakupan

  • lingkaran kehidupan: Cakupan
  • arti : Dalam setiap cakupan, instans layanan bersifat unik. Cakupan sering dikaitkan dengan siklus hidup suatu permintaan, misalnya dalam aplikasi web, cakupan baru dibuat dengan setiap permintaan HTTP.
  • Adegan yang berlaku: Cocok untuk layanan yang perlu berbagi instance dalam cakupan permintaan, seperti konteks database.

menggunakan layanan ini

Dalam fungsi Utama:

gambar-20240711095100016

rintisan_host,lulus_host.Services.GetRequiredService<MainWindow>();Dapatkan instance MainWindow.

Mengambil kelas MainWindow sebagai contoh, lihat konstruktor MainWindow di MainWindow.xaml.cs:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel;
    DataContext = this;
    InitializeComponent();

    Toggle_TitleButtonVisibility();

    _navigationService = navigationService;
    _navigationService.Navigating += OnNavigating;
    _navigationService.SetFrame(this.RootContentFrame);
    _navigationService.Navigate(typeof(DashboardPage));

    WindowChrome.SetWindowChrome(
        this,
        new WindowChrome
        {
            CaptionHeight = 50,
            CornerRadius = default,
            GlassFrameThickness = new Thickness(-1),
            ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
            UseAeroCaptionButtons = true
        }
    );

    this.StateChanged += MainWindow_StateChanged;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Setelah menghapus konten yang tidak relevan dengan topik ini, tindakannya adalah sebagai berikut:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel; 
    _navigationService = navigationService;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Pernahkah Anda memperhatikan bahwa Anda tidak perlu lagi membuat objek-objek ini sendiri? Pembuatan objek-objek ini dikelola oleh wadah injeksi ketergantungan. Ketika objek-objek ini diperlukan, mereka dapat disuntikkan melalui konstruktor seperti sekarang.

Jika injeksi ketergantungan tidak digunakan, tampilannya akan seperti ini:

public MainWindow()
{
    _serviceProvider = new IServiceProvider();
    ViewModel = new MainWindowViewModel(); 
    _navigationService = new INavigationService();  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Meringkaskan

Artikel ini pertama-tama memperkenalkan konsep injeksi ketergantungan, kemudian menjelaskan mengapa injeksi ketergantungan diperlukan, dan terakhir mempelajari cara menggunakan injeksi ketergantungan di WPF melalui proyek Galeri WPF.

mengacu pada

1、[WPF-Samples/Sample Applications/WPFGallery di utama · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)