Technologieaustausch

WPF/C#: So implementieren Sie die Abhängigkeitsinjektion in WPF

2024-07-12

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

Vorwort

Dieser Artikel verwendet das WPF Gallery-Projekt, um die zugehörigen Konzepte der Abhängigkeitsinjektion und die Durchführung der Abhängigkeitsinjektion in WPF kennenzulernen.

Was ist Abhängigkeitsinjektion?

Dependency Injection (DI) ist ein Entwurfsmuster, das zur Implementierung des Inversion of Control (IoC)-Prinzips verwendet wird. Der Hauptzweck der Abhängigkeitsinjektion besteht darin, die Erstellung von Objekten und die Verwaltung von Abhängigkeiten zwischen Objekten aus dem Objekt heraus in einen externen Container oder ein Framework zu übertragen und so die Wartbarkeit, Testbarkeit und Flexibilität des Codes zu verbessern.

Das Kernkonzept der Abhängigkeitsinjektion

  1. verlassen : Ein Objekt benötigt ein anderes Objekt, um seine Arbeit abzuschließen, dann hängt das erstere vom letzteren ab. Beispielsweise könnte eine OrderService-Klasse auf eine ProductRepository-Klasse angewiesen sein, um Produktinformationen abzurufen.
  2. Injektion : Übergeben Sie das abhängige Objekt an das Objekt, das es benötigt, anstatt das Objekt, das es benötigt, das abhängige Objekt selbst erstellen zu lassen. Die Injektion kann durch Konstruktoren, Eigenschaften oder Methodenparameter erreicht werden.
  3. Container : Ein Framework oder eine Bibliothek, die die Objekterstellung und -abhängigkeiten verwaltet. Der Container ist dafür verantwortlich, Objekte zu instanziieren, Abhängigkeiten aufzulösen und abhängige Objekte in Objekte einzufügen, die sie benötigen.

Arten der Abhängigkeitsinjektion

Konstruktorinjektion: Das abhängige Objekt wird durch den Konstruktor der Klasse übergeben.

public class OrderService
{
    private readonly IProductRepository _productRepository;

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

Immobilienspritze: Abhängige Objekte werden über die öffentlichen Eigenschaften der Klasse übergeben.

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

Methode Injektion: Das abhängige Objekt wird über den Methodenparameter der Klasse übergeben.

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

Warum eine Abhängigkeitsinjektion notwendig ist

Dependency Injection (DI) ist ein Entwurfsmuster, mit dem die Erstellung von Objekten und die Verwaltung von Abhängigkeiten zwischen Objekten aus dem Objekt heraus auf einen externen Container oder ein Framework übertragen werden können. Es gibt mehrere wichtige Gründe und Vorteile für die Abhängigkeitsinjektion:

  1. Kopplung reduzieren : Abhängigkeitsinjektion überträgt die Verwaltung von Abhängigkeiten aus dem Inneren des Objekts auf den externen Container, sodass das Objekt nicht wissen muss, wie es die Objekte erstellt, von denen es abhängt, sondern nur die Schnittstelle des abhängigen Objekts kennen muss. Dies kann die Kopplung zwischen Objekten erheblich reduzieren und den Code modularer und flexibler machen.
  2. Testbarkeit verbessern : Abhängigkeitsinjektion macht Unit-Tests einfacher und effizienter. Durch die Verwendung von Scheinobjekten oder Stubs anstelle tatsächlicher abhängiger Objekte können Entwickler Unit-Tests durchführen, ohne sich auf die tatsächliche Implementierung verlassen zu müssen. Dies trägt dazu bei, die Unabhängigkeit und Zuverlässigkeit des Tests sicherzustellen.
  3. Verbessern Sie die Wartbarkeit : Da die Abhängigkeitsinjektion die Kopplung zwischen Objekten verringert, wird der Code modularer und klarer. Dadurch ist der Code leichter zu verstehen und zu pflegen. Wenn Sie ein abhängiges Objekt ändern oder ersetzen müssen, müssen Sie nur die Konfigurations- oder Registrierungsinformationen ändern, ohne den Code zu ändern, der das Objekt verwendet.
  4. Flexibilität verbessern : Abhängigkeitsinjektion macht das System flexibler und kann abhängige Objekte leicht ersetzen, um unterschiedliche Funktionen oder Verhaltensweisen zu erreichen. Beispielsweise können unterschiedliche Implementierungen der Datenbankzugriffsschicht über Konfigurationsdateien oder Code umgeschaltet werden, ohne dass der Geschäftslogikcode geändert werden muss.
  5. Sorgentrennung fördern : Abhängigkeitsinjektion trägt dazu bei, eine Trennung von Bedenken zu erreichen, sodass sich jedes Objekt nur auf seine eigenen Verantwortlichkeiten konzentrieren muss, ohne sich darum zu kümmern, wie es die Objekte erstellt und erhält, von denen es abhängt. Dies trägt dazu bei, die Klarheit und Wartbarkeit des Codes zu verbessern.
  6. Unterstützen Sie Designmuster und Best Practices : Abhängigkeitsinjektion ist die Grundlage vieler Entwurfsmuster und Best Practices, wie z. B. Inversion of Control (IoC), Service Locator Pattern usw. Durch die Verwendung der Abhängigkeitsinjektion können Entwickler diese Muster und Praktiken einfacher implementieren und so die Codequalität und Skalierbarkeit verbessern.

So implementieren Sie die Abhängigkeitsinjektion

In diesem Artikel erfahren Sie anhand des WPF Gallery-Projekts, wie Sie die Abhängigkeitsinjektion in der WPF-Codeadresse verwenden:

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

Um die Abhängigkeitsinjektion in diesem Projekt zu implementieren, werden diese beiden Pakete verwendet:

Bild-20240711100435001

Schauen Sie sich zunächst den Inhalt von App.xaml.cs an:

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

Bild-20240711083011393

Was ist IHost?

In C#,IHost ist eine Schnittstelle, die in .NET zum Erstellen und Konfigurieren von Anwendungen verwendet wirdHostKonzept abstrakt.IHost Eine Schnittstelle definiert eine Sammlung von Diensten und Komponenten, die zum Starten, Ausführen und Verwalten einer Anwendung erforderlich sind. Es wird normalerweise mit ASP.NET Core-Anwendungen verwendet, funktioniert aber auch mit anderen Arten von .NET-Anwendungen, wie Konsolenanwendungen oder WPF-Programmen.

Bild-20240711082817268

IHostDie Schnittstelle besteht ausHostBuilderKlassenimplementierung, die die Erstellung und Konfiguration von ermöglichtIHostInstanzmethoden.HostBuilderErmöglicht Ihnen das Hinzufügen verschiedener Dienste wie Protokollierung, Konfiguration, Abhängigkeitsinjektionscontainer usw. sowie das Konfigurieren des Start- und Stoppverhaltens Ihrer Anwendung.

Bild-20240711083048854

Bild-20240711083156306

Bietet praktische Methoden zum Erstellen von Microsoft.Extensions.Hosting.IHostBuilder-Instanzen mit vorkonfigurierten Standardwerten.

Bild-20240711083713204

Gibt einen IHostBuilder zurück.

Bild-20240711084145756

Bild-20240711084211035

Fügen Sie dem Container Dienste hinzu. Dieser Vorgang kann mehrmals aufgerufen werden und seine Ergebnisse sind kumulativ.

Die Bedeutung des Parameters configureDelegate besteht darin, den Delegaten von Microsoft.Extensions.DependencyInjection.IServiceCollection zu konfigurieren.
Diese Sammlung wird zum Erstellen von System.IServiceProvider verwendet.

Der Delegat erfordert zwei Parametertypen: HostBuilderContext und IServiceCollection, die keinen Rückgabewert haben.

Bild-20240711084853971

Hier wird ein Lambda-Ausdruck übergeben, der den Delegatentyp erfüllt.

In C#,() => {} Ist eine Syntax für Lambda-Ausdrücke. Lambda-Ausdrücke sind ein einfacher Delegaten-Wrapper, mit dem Sie eine anonyme Methode definieren und als Parameter an eine Methode übergeben können, die Delegaten oder Ausdrucksbäume unterstützt.

Lambda-Ausdrücke bieten eine übersichtliche Möglichkeit zum Definieren von Methoden und sind insbesondere dann nützlich, wenn Sie Methoden als Parameter an andere Methoden übergeben müssen.

Bild-20240711085344696

Beim Hinzufügen von Diensten gibt es hier zwei Lebenszyklen: Neben AddSingleton und AddTransient gibt es auch AddScoped.

Diese Methoden definieren den Lebenszyklus des Dienstes, d. h. wie Dienstinstanzen in der Anwendung erstellt und verwaltet werden.

AddSingleton

  • Lebenszyklus:Singleton
  • Bedeutung : Während des gesamten Anwendungslebenszyklus wird nur eine Dienstinstanz erstellt. Unabhängig davon, wie oft Sie es vom Container anfordern, wird dieselbe Instanz zurückgegeben.
  • Anwendbare Szene: Geeignet für zustandslose Dienste oder Ressourcen, die in der gesamten Anwendung gemeinsam genutzt werden, z. B. Konfigurationen, Logger usw.

AddTransient

  • Lebenszyklus: Vorübergehend
  • Bedeutung: Jedes Mal, wenn ein Dienst vom Container angefordert wird, wird eine neue Instanz erstellt.
  • Anwendbare Szene: Geeignet für zustandsbehaftete Dienste oder Szenarien, die für jede Anfrage eine neue Instanz erfordern, z. B. Seiten, Ansichtsmodelle usw.

AddScoped

  • Lebenszyklus: Zielfernrohr
  • Bedeutung : Innerhalb jedes Bereichs sind Dienstinstanzen eindeutig. Bereiche sind oft mit dem Lebenszyklus einer Anfrage verknüpft, beispielsweise wird in Webanwendungen mit jeder HTTP-Anfrage ein neuer Bereich erstellt.
  • Anwendbare Szene: Geeignet für Dienste, die Instanzen innerhalb des Anforderungsbereichs gemeinsam nutzen müssen, z. B. Datenbankkontexte.

diese Dienste nutzen

In der Hauptfunktion:

Bild-20240711095100016

Start-up_host,passieren_host.Services.GetRequiredService<MainWindow>();Rufen Sie die MainWindow-Instanz ab.

Sehen Sie sich am Beispiel der MainWindow-Klasse den Konstruktor von MainWindow in MainWindow.xaml.cs an:

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

Nach dem Entfernen der für dieses Thema irrelevanten Inhalte sieht es wie folgt aus:

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

Ist Ihnen aufgefallen, dass Sie diese Objekte nicht mehr selbst neu erstellen müssen? Die Erstellung dieser Objekte wird vom Abhängigkeitsinjektionscontainer verwaltet. Wenn diese Objekte benötigt werden, können sie wie bisher über den Konstruktor eingefügt werden.

Wenn keine Abhängigkeitsinjektion verwendet wird, könnte es so aussehen:

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

Zusammenfassen

In diesem Artikel wird zunächst das Konzept der Abhängigkeitsinjektion vorgestellt, dann erklärt, warum eine Abhängigkeitsinjektion erforderlich ist, und schließlich wird über das WPF Gallery-Projekt erläutert, wie die Abhängigkeitsinjektion in WPF verwendet wird.

beziehen auf

1. [WPF-Samples/Beispielanwendungen/WPFGallery auf der Hauptseite · Microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Beispielanwendungen/WPFGallery)