Udostępnij za pośrednictwem


Wstrzykiwanie zależności

Napiwek

Ta zawartość jest fragmentem książki eBook, wzorców aplikacji dla przedsiębiorstw przy użyciu platformy .NET, dostępnej na platformie .NET MAUIDocs lub jako bezpłatnego pliku PDF do pobrania, który można odczytać w trybie offline.

Wzorce aplikacji dla przedsiębiorstw przy użyciu miniatury książki eBook platformy .NET MAUI .

Zazwyczaj konstruktor klasy jest wywoływany podczas tworzenia wystąpienia obiektu, a wszystkie wartości, których potrzebuje obiekt, są przekazywane jako argumenty do konstruktora. Jest to przykład wstrzykiwania zależności znany jako wstrzykiwanie konstruktora. Zależności wymagane przez obiekt są wstrzykiwane do konstruktora.

Określając zależności jako typy interfejsów, iniekcja zależności umożliwia oddzielenie konkretnych typów od kodu, który zależy od tych typów. Zazwyczaj używa kontenera, który zawiera listę rejestracji i mapowań między interfejsami i typami abstrakcyjnymi oraz konkretne typy, które implementują lub rozszerzają te typy.

Istnieją również inne typy wstrzykiwania zależności, takie jak wstrzykiwanie metody setter właściwości i wstrzykiwanie wywołań metody, ale są one mniej często spotykane. W związku z tym w tym rozdziale skupimy się wyłącznie na wykonywaniu wstrzykiwania konstruktora z kontenerem iniekcji zależności.

Wprowadzenie do wstrzykiwania zależności

Wstrzykiwanie zależności jest wyspecjalizowaną wersją wzorca Inversion of Control (IoC), gdzie problemem jest proces uzyskiwania wymaganej zależności. W przypadku iniekcji zależności inna klasa jest odpowiedzialna za wstrzykiwanie zależności do obiektu w czasie wykonywania. Poniższy przykład kodu pokazuje, jak ProfileViewModel klasa jest ustrukturyzowana podczas korzystania z iniekcji zależności:

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

Konstruktor ProfileViewModel odbiera wiele wystąpień obiektów interfejsu jako argumenty wprowadzone przez inną klasę. Jedyną zależnością ProfileViewModel w klasie są typy interfejsów. ProfileViewModel W związku z tym klasa nie ma żadnej wiedzy na temat klasy odpowiedzialnej za utworzenie wystąpienia obiektów interfejsu. Klasa, która jest odpowiedzialna za utworzenie wystąpienia obiektów interfejsu i wstawienie jej do ProfileViewModel klasy, jest znana jako kontener wstrzykiwania zależności.

Kontenery iniekcji zależności zmniejszają sprzężenie między obiektami, zapewniając obiekt do tworzenia wystąpień klas i zarządzania ich okresem istnienia na podstawie konfiguracji kontenera. Podczas tworzenia obiektu kontener wprowadza do niego wszelkie zależności wymagane przez obiekt. Jeśli te zależności nie zostały jeszcze utworzone, kontener tworzy i rozwiązuje swoje zależności jako pierwsze.

Korzystanie z kontenera wstrzykiwania zależności ma kilka zalet:

  • Kontener usuwa potrzebę zlokalizowania jego zależności i zarządzania okresami istnienia klasy.
  • Kontener umożliwia mapowanie wdrożonych zależności bez wpływu na klasę.
  • Kontener ułatwia testowanie, umożliwiając pozorowanie zależności.
  • Kontener zwiększa łatwość konserwacji, umożliwiając łatwe dodawanie nowych klas do aplikacji.

W kontekście aplikacji .NET MAUI korzystającej z maszyny MVVM kontener wstrzykiwania zależności będzie zwykle używany do rejestrowania i rozpoznawania widoków, rejestrowania i rozpoznawania modeli widoków oraz rejestrowania usług i wstrzykiwania ich do modeli wyświetlania.

Na platformie .NET jest dostępnych wiele kontenerów iniekcji zależności; Aplikacja wieloplatformowa eShop używa Microsoft.Extensions.DependencyInjection do zarządzania wystąpieniami widoków, wyświetlania modeli i klas usług w aplikacji. Microsoft.Extensions.DependencyInjection ułatwia tworzenie luźno powiązanych aplikacji i udostępnia wszystkie funkcje powszechnie spotykane w kontenerach iniekcji zależności, w tym metody rejestrowania mapowań typów i wystąpień obiektów, rozpoznawania obiektów, zarządzania okresami istnienia obiektów i wstrzykiwania obiektów zależnych do konstruktorów rozpoznawanych obiektów. Aby uzyskać więcej informacji na temat Microsoft.Extensions.DependencyInjectionprogramu , zobacz Wstrzykiwanie zależności na platformie .NET.

Na platformie .NET MAUIMauiProgram klasa wywoła metodę w CreateMauiApp celu utworzenia MauiAppBuilder obiektu. Obiekt MauiAppBuilder ma Services właściwość typu IServiceCollection, która udostępnia miejsce do rejestrowania naszych składników, takich jak widoki, wyświetlanie modeli i usługi na potrzeby wstrzykiwania zależności. Wszystkie składniki zarejestrowane we Services właściwości zostaną dostarczone do kontenera wstrzykiwania zależności po wywołaniu MauiAppBuilder.Build metody.

W czasie wykonywania kontener musi wiedzieć, która implementacja usług jest żądana, aby utworzyć wystąpienie dla żądanych obiektów. W aplikacji IAppEnvironmentServicewieloplatformowej eShop należy rozpoznać interfejsy , IDialogService i INavigationServiceISettingsService , zanim będzie można utworzyć ProfileViewModel wystąpienie obiektu. Obejmuje to kontener wykonujący następujące akcje:

  • Podjęcie decyzji o utworzeniu wystąpienia obiektu, który implementuje interfejs. Jest to nazywane rejestracją.
  • Utworzenie wystąpienia obiektu, który implementuje wymagany interfejs i ProfileViewModel obiekt. Jest to nazywane rozwiązaniem.

W końcu aplikacja zakończy korzystanie z ProfileViewModel obiektu i stanie się dostępna do odzyskiwania pamięci. W tym momencie moduł odśmiecywania pamięci powinien usuwać wszystkie krótkotrwałe implementacje interfejsu, jeśli inne klasy nie współużytkują tego samego wystąpienia.

Rejestracja

Przed wstrzyknięciem zależności do obiektu należy najpierw zarejestrować typy zależności w kontenerze. Rejestrowanie typu obejmuje przekazanie kontenera interfejsu i konkretnego typu, który implementuje interfejs.

Istnieją dwa sposoby rejestrowania typów i obiektów w kontenerze za pomocą kodu:

  • Zarejestruj typ lub mapowanie w kontenerze. Jest to nazywane rejestracją przejściowymi. W razie potrzeby kontener utworzy wystąpienie określonego typu.
  • Zarejestruj istniejący obiekt w kontenerze jako pojedynczy obiekt. W razie potrzeby kontener zwróci odwołanie do istniejącego obiektu.

Uwaga

Kontenery wstrzykiwania zależności nie zawsze są odpowiednie. Wstrzykiwanie zależności wprowadza dodatkową złożoność i wymagania, które mogą nie być odpowiednie lub przydatne dla małych aplikacji. Jeśli klasa nie ma żadnych zależności lub nie jest zależnością dla innych typów, może nie mieć sensu umieścić jej w kontenerze. Ponadto jeśli klasa ma jeden zestaw zależności, które są integralną częścią typu i nigdy się nie zmieni, może nie mieć sensu umieścić go w kontenerze.

Rejestracja typów wymagających wstrzykiwania zależności powinna być wykonywana w jednej metodzie w aplikacji. Ta metoda powinna być wywoływana na wczesnym etapie cyklu życia aplikacji, aby upewnić się, że jest świadoma zależności między jej klasami. Aplikacja wieloplatformowa eShop wykonuje tę metodę MauiProgram.CreateMauiApp . Poniższy przykład kodu pokazuje, jak aplikacja wieloplatformowa eShop deklaruje klasę CreateMauiAppMauiProgram w klasie:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

Metoda MauiApp.CreateBuilder tworzy MauiAppBuilder obiekt, którego możemy użyć do zarejestrowania naszych zależności. Wiele zależności w wieloplatformowej aplikacji eShop musi być zarejestrowanych, więc metody RegisterAppServicesrozszerzeń , RegisterViewModelsi RegisterViews zostały utworzone w celu zapewnienia zorganizowanego i konserwowalnego przepływu pracy rejestracji. Poniższy kod przedstawia metodę RegisterViewModels :

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

Ta metoda odbiera wystąpienie MauiAppBuilderklasy i możemy użyć właściwości do zarejestrowania Services modeli widoków. W zależności od potrzeb aplikacji może być konieczne dodanie usług z różnymi okresami istnienia. Poniższa tabela zawiera informacje na temat tego, kiedy można wybrać te różne okresy istnienia rejestracji:

Metoda opis
AddSingleton<T> Utworzy pojedyncze wystąpienie obiektu, które pozostanie przez cały okres istnienia aplikacji.
AddTransient<T> Utworzy nowe wystąpienie obiektu podczas żądania podczas rozwiązywania problemu. Obiekty przejściowe nie mają wstępnie zdefiniowanego okresu istnienia, ale zwykle będą zgodne z okresem istnienia ich hosta.

Uwaga

Modele widoku nie dziedziczą z interfejsu, dlatego potrzebują tylko konkretnego typu dostarczonego AddSingleton<T> do metod i AddTransient<T> .

Element CatalogViewModel jest używany w pobliżu katalogu głównego aplikacji i powinien być zawsze dostępny, więc zarejestrowanie go AddSingleton<T> w usłudze jest korzystne. Inne modele widoków, takie jak CheckoutViewModel i OrderDetailViewModel , są w sytuacji nawigowane do lub są używane później w aplikacji. Załóżmy, że wiesz, że masz składnik, który może nie być zawsze używany. W takim przypadku, jeśli jest to pamięć lub obliczenia intensywnie korzystające lub wymaga danych just in time, może to być lepszy kandydat do AddTransient<T> rejestracji.

Innym typowym sposobem dodawania usług jest użycie AddSingleton<TService, TImplementation> metod i AddTransient<TService, TImplementation> . Te metody przyjmują dwa typy danych wejściowych: definicję interfejsu i konkretną implementację. Ten typ rejestracji jest najlepszy w przypadkach, w których wdrażasz usługi na podstawie interfejsów. W poniższym przykładzie kodu rejestrujemy nasz ISettingsService interfejs przy użyciu implementacji SettingsService :

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Po zarejestrowaniu wszystkich usług należy wywołać metodę MauiAppBuilder.Build , aby utworzyć MauiApp nasz i wypełnić kontener wstrzykiwania zależności wszystkimi zarejestrowanymi usługami.

Ważne

Po wywołaniu Build metody kontener wstrzykiwania zależności jest niezmienny i nie można go już aktualizować ani modyfikować. Upewnij się, że wszystkie usługi potrzebne w aplikacji zostały zarejestrowane przed wywołaniem metody Build.

Rozwiązanie

Po zarejestrowaniu typu można go rozpoznać lub wprowadzić jako zależność. Gdy typ jest rozpoznawany, a kontener musi utworzyć nowe wystąpienie, wprowadza wszystkie zależności do wystąpienia.

Ogólnie rzecz biorąc, gdy typ jest rozpoznawany, występuje jedna z trzech rzeczy:

  1. Jeśli typ nie został zarejestrowany, kontener zgłasza wyjątek.
  2. Jeśli typ został zarejestrowany jako pojedynczy, kontener zwraca pojedyncze wystąpienie. Jeśli ten typ jest wywoływany po raz pierwszy, kontener tworzy go, jeśli jest to wymagane, i przechowuje odwołanie do niego.
  3. Jeśli typ został zarejestrowany jako przejściowy, kontener zwraca nowe wystąpienie i nie obsługuje odwołania do niego.

Platforma .NET MAUI oferuje wiele sposobów rozwiązywania zarejestrowanych składników na podstawie Twoich potrzeb. Najbardziej bezpośrednim sposobem uzyskania dostępu do kontenera wstrzykiwania zależności jest Element użycie elementu Handler.MauiContext.Services. Poniżej przedstawiono przykład:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Może to być przydatne, jeśli musisz rozwiązać problem z usługą Element z poziomu konstruktora lub spoza konstruktora Element.

Uwaga

Istnieje możliwość, że Handler właściwość może Element mieć wartość null, dlatego należy pamiętać, że może być konieczne obsłużenie tych sytuacji. Aby uzyskać więcej informacji, zapoznaj się z cyklem życia programu obsługi w Centrum dokumentacji firmy Microsoft.

Jeśli używasz kontrolki Shell dla platformy .NET MAUI, niejawnie wywoła kontener wstrzykiwania zależności, aby utworzyć nasze obiekty podczas nawigacji. Podczas konfigurowania Routing.RegisterRoute kontrolki Shell metoda będzie wiązać ścieżkę trasy do elementuView, jak pokazano w poniższym przykładzie:

Routing.RegisterRoute("Filter", typeof(FiltersView));

Podczas Shell nawigacji wyszuka ona rejestracje FiltersViewelementu , a jeśli zostanie znaleziona, utworzy ten widok i wstrzykuje wszystkie zależności do konstruktora. Jak pokazano w poniższym przykładzie kodu, CatalogViewModel element zostanie wstrzyknięty do elementu FiltersView:

namespace eShop.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Napiwek

Kontener wstrzykiwania zależności doskonale nadaje się do tworzenia wystąpień modelu widoku. Jeśli model widoku ma zależności, będzie obsługiwać tworzenie i wstrzykiwanie wszelkich wymaganych usług. Upewnij się, że rejestrujesz modele widoku i wszelkie zależności, które mogą mieć z CreateMauiApp metodą w MauiProgram klasie .

Podsumowanie

Wstrzykiwanie zależności umożliwia oddzielenie konkretnych typów od kodu, który zależy od tych typów. Zazwyczaj używa kontenera, który zawiera listę rejestracji i mapowań między interfejsami i typami abstrakcyjnymi oraz konkretne typy, które implementują lub rozszerzają te typy.

Microsoft.Extensions.DependencyInjection ułatwia tworzenie luźno powiązanych aplikacji i udostępnia wszystkie funkcje powszechnie spotykane w kontenerach iniekcji zależności, w tym metody rejestrowania mapowań typów i wystąpień obiektów, rozpoznawania obiektów, zarządzania okresami istnienia obiektów i wstrzykiwania obiektów zależnych do konstruktorów rozpoznawanych obiektów.