Wstrzykiwanie zależności
Interfejs użytkownika aplikacji wieloplatformowej platformy .NET (.NET MAUI) zapewnia wbudowaną obsługę korzystania z 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.
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.
Uwaga
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 rzadziej używane.
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.
Kontenery wstrzykiwania zależności
Jeśli klasa nie tworzy bezpośrednio wystąpień obiektów, których potrzebuje, inna klasa musi przejąć tę odpowiedzialność. Rozważmy poniższy przykład, który przedstawia klasę modelu widoku, która wymaga argumentów konstruktora:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
W tym przykładzie MainPageViewModel
konstruktor wymaga dwóch wystąpień obiektu interfejsu jako argumentów wstrzykiwanych przez inną klasę. Jedyną zależnością MainPageViewModel
w klasie są typy interfejsów. MainPageViewModel
W związku z tym klasa nie ma żadnej wiedzy na temat klasy odpowiedzialnej za utworzenie wystąpienia obiektów interfejsu.
Podobnie rozważmy następujący przykład pokazujący klasę strony, która wymaga argumentu konstruktora:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
W tym przykładzie MainPage
konstruktor wymaga konkretnego typu jako argumentu wstrzykiwanego przez inną klasę. Jedyną zależnością MainPage
w klasie jest MainPageViewModel
typ . MainPage
W związku z tym klasa nie ma żadnej wiedzy na temat klasy odpowiedzialnej za utworzenie wystąpienia konkretnego typu.
W obu przypadkach klasa odpowiedzialna za utworzenie wystąpienia zależności i wstawienie ich do klasy zależnej jest nazywana kontenerem 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 wszelkie zależności wymagane przez obiekt. Jeśli te zależności nie zostały utworzone, kontener najpierw utworzy i rozwiąże swoje zależności.
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 MAUI platformy .NET, która używa wzorca Model-View-ViewModel (MVVM), kontener iniekcji 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 widoku. Aby uzyskać więcej informacji na temat wzorca MVVM, zobacz Model-View-ViewModel (MVVM).
Istnieje wiele kontenerów iniekcji zależności dostępnych dla platformy .NET. Program .NET MAUI ma wbudowaną obsługę zarządzania Microsoft.Extensions.DependencyInjection tworzeniem wystąpień widoków, widoków 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.
W czasie wykonywania kontener musi wiedzieć, która implementacja zależności jest żądana, aby utworzyć wystąpienie dla żądanych obiektów. W powyższym ILoggingService
przykładzie interfejsy i ISettingsService
muszą zostać rozwiązane, zanim MainPageViewModel
obiekt będzie można utworzyć wystąpienie. 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ą. Aby uzyskać więcej informacji, zobacz Rejestracja.
- Utworzenie wystąpienia obiektu, który implementuje wymagany interfejs i
MainPageViewModel
obiekt. Jest to nazywane rozwiązaniem. Aby uzyskać więcej informacji, zobacz Rozwiązanie.
W końcu aplikacja zakończy korzystanie z MainPageViewModel
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ą tych samych wystąpień.
Rejestracja
Przed wstrzyknięciem zależności do obiektu należy najpierw zarejestrować typy zależności w kontenerze. Rejestrowanie typu zwykle polega na przekazaniu kontenera konkretnego typu lub interfejsu i konkretnego typu, który implementuje interfejs.
Istnieją dwa główne podejścia do rejestrowania typów i obiektów w kontenerze:
- 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 dla aplikacji .NET MAUI. Wstrzykiwanie zależności wprowadza dodatkową złożoność i wymagania, które mogą nie być odpowiednie lub przydatne dla mniejszych 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ć ich 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. Aplikacje powinny zwykle wykonywać tę operację w metodzie CreateMauiApp
MauiProgram
w klasie . Klasa MauiProgram
wywołuje metodę CreateMauiApp
w celu utworzenia MauiAppBuilder obiektu. Obiekt MauiAppBuilder ma Services właściwość typu IServiceCollection, która udostępnia miejsce do rejestrowania typów, takich jak widoki, modele widoków i usługi do wstrzykiwania zależności:
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();
}
}
Typy zarejestrowane we Services właściwości są dostarczane do kontenera iniekcji zależności, gdy MauiAppBuilder.Build() jest wywoływany.
Podczas rejestrowania zależności należy zarejestrować wszystkie zależności, w tym wszelkie typy, które wymagają zależności. W związku z tym jeśli masz model widoków, który przyjmuje zależność jako parametr konstruktora, musisz zarejestrować model widoku wraz ze wszystkimi jego zależnościami. Podobnie, jeśli masz widok, który przyjmuje zależność modelu widoku jako parametr konstruktora, musisz zarejestrować widok i model widoku wraz ze wszystkimi jego zależnościami.
Napiwek
Kontener iniekcji zależności jest idealny do tworzenia wystąpień modelu widoku. Jeśli model widoku ma zależności, będzie zarządzać tworzeniem i iniekcją wszelkich wymaganych usług. Upewnij się, że rejestrujesz modele widoków i wszelkie zależności, które mogą znajdować się w CreateMauiApp
metodzie MauiProgram
w klasie .
W aplikacji Shell nie musisz rejestrować stron w kontenerze iniekcji zależności, chyba że chcesz wpłynąć na okres istnienia strony względem kontenera za pomocą AddSingleton
metody , AddTransient
lub AddScoped
. Aby uzyskać więcej informacji, zobacz Okres istnienia zależności.
Okres istnienia zależności
W zależności od potrzeb aplikacji może być konieczne zarejestrowanie zależności z różnymi okresami istnienia. W poniższej tabeli wymieniono główne metody, których można użyć do rejestrowania zależności oraz ich okresów istnienia rejestracji:
Metoda | opis |
---|---|
AddSingleton<T> |
Tworzy pojedyncze wystąpienie obiektu, które pozostanie przez cały okres istnienia aplikacji. |
AddTransient<T> |
Tworzy nowe wystąpienie obiektu podczas żądania podczas rozpoznawania. Obiekty przejściowe nie mają wstępnie zdefiniowanego okresu istnienia, ale zwykle będą zgodne z okresem istnienia ich hosta. |
AddScoped<T> |
Tworzy wystąpienie obiektu, które współudzieli okres istnienia hosta. Gdy host wykracza poza zakres, robi to jego zależność. W związku z tym rozpoznawanie tej samej zależności wiele razy w tym samym zakresie daje to samo wystąpienie, podczas gdy rozpoznawanie tej samej zależności w różnych zakresach spowoduje uzyskanie różnych wystąpień. |
Uwaga
Jeśli obiekt nie dziedziczy z interfejsu, takiego jak widok lub model-widok, należy podać tylko jego konkretny typ do AddSingleton<T>
metody , AddTransient<T>
lub AddScoped<T>
.
Klasa MainPageViewModel
jest używana w pobliżu katalogu głównego aplikacji i powinna być zawsze dostępna, więc zarejestrowanie jej AddSingleton<T>
w usłudze jest korzystne. Inne modele wyświetlania mogą być w sytuacji nawigowane lub używane później w aplikacji. Jeśli masz typ, który może nie być zawsze używany lub jest intensywnie obciążający pamięć lub wymaga danych just in time, może to być lepszy kandydat do AddTransient<T>
rejestracji.
Innym typowym sposobem rejestrowania zależności jest użycie AddSingleton<TService, TImplementation>
metod , AddTransient<TService, TImplementation>
lub AddScoped<TService, TImplementation>
. Metody te przyjmują dwa typy — definicję interfejsu i konkretną implementację. Ten typ rejestracji jest najlepszy w przypadkach, w których wdrażasz usługi na podstawie interfejsów.
Po zarejestrowaniu MauiAppBuilder.Build() wszystkich typów należy wywołać metodę MauiApp , aby utworzyć obiekt i wypełnić kontener wstrzykiwania zależności wszystkimi zarejestrowanymi typami.
Ważne
Po MauiAppBuilder.Build() wywołaniu wywołania typy zarejestrowane w kontenerze wstrzykiwania zależności będą niezmienne i nie będą już mogły być aktualizowane ani modyfikowane.
Rejestrowanie zależności za pomocą metody rozszerzenia
Metoda MauiApp.CreateBuilder tworzy MauiAppBuilder obiekt, który może służyć do rejestrowania zależności. Jeśli aplikacja musi zarejestrować wiele zależności, możesz utworzyć metody rozszerzenia, aby ułatwić organizowanie i konserwację przepływu pracy rejestracji:
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;
}
}
W tym przykładzie trzy metody rozszerzenia rejestracji używają MauiAppBuilder wystąpienia, aby uzyskać dostęp do właściwości w celu zarejestrowania Services zależności.
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 jeden z trzech scenariuszy:
- Jeśli typ nie został zarejestrowany, kontener zgłasza wyjątek.
- 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.
- Jeśli typ został zarejestrowany jako przejściowy, kontener zwraca nowe wystąpienie i nie obsługuje odwołania do niego.
Program .NET MAUI obsługuje automatyczne i jawne rozpoznawanie zależności. Automatyczne rozpoznawanie zależności używa iniekcji konstruktora bez jawnego żądania zależności z kontenera. Jawne rozwiązywanie zależności występuje na żądanie przez jawne żądanie zależności z kontenera.
Automatyczne rozwiązywanie zależności
Automatyczne rozpoznawanie zależności występuje w aplikacjach korzystających z powłoki .NET MAUI, pod warunkiem, że zarejestrowano typ zależności i typ, który używa zależności z kontenerem wstrzykiwania zależności.
Podczas nawigacji opartej na powłoce program .NET MAUI wyszuka rejestracje stron, a jeśli zostanie znaleziony, utworzy stronę i wstrzykuje wszystkie zależności do konstruktora:
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
W tym przykładzie MainPage
konstruktor odbiera MainPageViewModel
wstrzyknięte wystąpienie. Z kolei MainPageViewModel
wystąpienie ma ILoggingService
wstrzyknięte wystąpienia i ISettingsService
:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
{
_loggingService = loggingService;
_settingsService = settingsService;
}
}
Ponadto w aplikacji opartej na powłoce program .NET MAUI wprowadza zależności na stronach szczegółów zarejestrowanych w metodzie Routing.RegisterRoute .
Jawne rozpoznawanie zależności
Aplikacja oparta na powłoce nie może używać wstrzykiwania konstruktora, gdy typ uwidacznia tylko konstruktor bez parametrów. Alternatywnie, jeśli aplikacja nie używa powłoki, musisz użyć jawnego rozpoznawania zależności.
Dostęp do kontenera iniekcji zależności można jawnie uzyskać za Element pośrednictwem właściwości Handler.MauiContext.Service
, która jest typu IServiceProvider:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
HandlerChanged += OnHandlerChanged;
}
void OnHandlerChanged(object sender, EventArgs e)
{
BindingContext = Handler.MauiContext.Services.GetService<MainPageViewModel>();
}
}
Takie podejście może być przydatne, jeśli trzeba rozpoznać zależność z Elementklasy lub spoza konstruktora Element. W tym przykładzie uzyskanie dostępu do kontenera wstrzykiwania zależności w HandlerChanged
procedurze obsługi zdarzeń gwarantuje, że program obsługi został ustawiony dla strony i dlatego Handler
właściwość nie będzie .null
Ostrzeżenie
Właściwość Handler
może mieć null
wartość Element
, dlatego należy pamiętać, że może być konieczne uwzględnienie tej sytuacji. Aby uzyskać więcej informacji, zobacz Cykl życia programu obsługi.
W modelu widoku kontener wstrzykiwania zależności można jawnie uzyskać dostęp za pośrednictwem Handler.MauiContext.Service
właściwości 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>();
}
}
W modelu widoku kontener wstrzykiwania zależności można jawnie uzyskać dostęp za pośrednictwem Handler.MauiContext.Service
właściwości Window.Page
:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel()
{
_loggingService = Application.Current.Windows[0].Page.Handler.MauiContext.Services.GetService<ILoggingService>();
_settingsService = Application.Current.Windows[0].Page.Handler.MauiContext.Services.GetService<ISettingsService>();
}
}
Wadą tego podejścia jest to, że model widoku ma teraz zależność od Application typu. Tę wadę można jednak wyeliminować, przekazując IServiceProvider argument do konstruktora modelu widoku. Element IServiceProvider jest rozpoznawany za pomocą automatycznego rozwiązywania zależności bez konieczności rejestrowania go w kontenerze iniekcji zależności. W przypadku tego podejścia typ i jego IServiceProvider zależność można automatycznie rozpoznać, pod warunkiem, że typ jest zarejestrowany w kontenerze wstrzykiwania zależności. Następnie IServiceProvider można go użyć do jawnego rozwiązywania zależności:
public class MainPageViewModel
{
readonly ILoggingService _loggingService;
readonly ISettingsService _settingsService;
public MainPageViewModel(IServiceProvider serviceProvider)
{
_loggingService = serviceProvider.GetService<ILoggingService>();
_settingsService = serviceProvider.GetService<ISettingsService>();
}
}
Ponadto IServiceProvider dostęp do wystąpienia można uzyskać na każdej platformie za pośrednictwem IPlatformApplication.Current.Services
właściwości .
Ograniczenia dotyczące zasobów XAML
Typowym scenariuszem jest zarejestrowanie strony w kontenerze wstrzykiwania zależności i użycie automatycznego rozpoznawania zależności w celu wstrzyknięcia go do App
konstruktora i ustawienie jej jako wartości MainPage
właściwości:
public App(MyFirstAppPage page)
{
InitializeComponent();
MainPage = page;
}
Typowym scenariuszem jest zarejestrowanie strony w kontenerze wstrzykiwania zależności i użycie automatycznego rozpoznawania zależności w celu wstrzyknięcia go do App
konstruktora i ustawienie jej jako pierwszej strony, która ma być wyświetlana w aplikacji:
MyFirstAppPage _firstPage;
public App(MyFirstAppPage page)
{
InitializeComponent();
_firstPage = page;
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(_firstPage);
}
Jednak w tym scenariuszu próba MyFirstAppPage
uzyskania dostępu do obiektu zadeklarowanego StaticResource
w języku XAML w słowniku App
XamlParseException zasobów spowoduje zgłoszenie komunikatu podobnego do Position {row}:{column}. StaticResource not found for key {key}
. Dzieje się tak, ponieważ strona rozwiązana przez wstrzyknięcie konstruktora została utworzona przed zainicjowaniem zasobów XAML na poziomie aplikacji.
Obejściem tego problemu jest wstrzyknięcie elementu IServiceProvider do App
klasy, a następnie użycie go do rozwiązania strony wewnątrz App
klasy:
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
MainPage = serviceProvider.GetService<MyFirstAppPage>();
}
MyFirstAppPage _firstPage;
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
_firstPage = serviceProvider.GetService<MyFirstAppPage>();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(_firstPage);
}
To podejście wymusza utworzenie i zainicjowanie drzewa obiektów XAML przed rozwiązaniem strony.