Afhankelijkheidsinjectie
Tip
Deze inhoud is een fragment uit het eBook, Enterprise Application Patterns Using .NET MAUI, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.
Normaal gesproken wordt een klasseconstructor aangeroepen bij het instantiëren van een object en eventuele waarden die het object nodig heeft, als argumenten doorgegeven aan de constructor. Dit is een voorbeeld van afhankelijkheidsinjectie, ook wel constructorinjectie genoemd. De afhankelijkheden die het object nodig heeft, worden in de constructor geïnjecteerd.
Door afhankelijkheden op te geven als interfacetypen, kan afhankelijkheidsinjectie de betontypen loskoppelen van de code die afhankelijk is van deze typen. Over het algemeen wordt een container gebruikt die een lijst met registraties en toewijzingen tussen interfaces en abstracte typen bevat, en de concrete typen die deze typen implementeren of uitbreiden.
Er zijn ook andere soorten afhankelijkheidsinjectie, zoals eigenschapssetterinjectie en methodeaanroepinjectie, maar ze worden minder vaak gezien. Daarom richt dit hoofdstuk zich alleen op het uitvoeren van constructorinjectie met een container voor afhankelijkheidsinjectie.
Inleiding tot afhankelijkheidsinjectie
Afhankelijkheidsinjectie is een gespecialiseerde versie van het IoC-patroon (Inversion of Control), waarbij het probleem dat wordt omgekeerd, het proces is om de vereiste afhankelijkheid te verkrijgen. Met afhankelijkheidsinjectie is een andere klasse verantwoordelijk voor het injecteren van afhankelijkheden in een object tijdens runtime. In het volgende codevoorbeeld ziet u hoe de ProfileViewModel
klasse is gestructureerd bij het gebruik van afhankelijkheidsinjectie:
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
}
De ProfileViewModel
constructor ontvangt meerdere interfaceobjectexemplaren als argumenten die door een andere klasse zijn geïnjecteerd. De enige afhankelijkheid in de ProfileViewModel
klasse is op de interfacetypen. Daarom heeft de ProfileViewModel
klasse geen kennis van de klasse die verantwoordelijk is voor het instantiëren van de interfaceobjecten. De klasse die verantwoordelijk is voor het instantiëren van de interfaceobjecten en het invoegen ervan in de ProfileViewModel
klasse, wordt de container voor afhankelijkheidsinjectie genoemd.
Afhankelijkheidsinjectiecontainers verminderen de koppeling tussen objecten door een faciliteit te bieden om klasse-exemplaren te instantiëren en hun levensduur te beheren op basis van de configuratie van de container. Tijdens het maken van objecten injecteert de container eventuele afhankelijkheden die het object nodig heeft. Als deze afhankelijkheden nog niet zijn gemaakt, maakt en lost de container eerst hun afhankelijkheden op.
Er zijn verschillende voordelen voor het gebruik van een container voor afhankelijkheidsinjectie:
- Een container verwijdert de noodzaak voor een klasse om de afhankelijkheden te vinden en de levensduur ervan te beheren.
- Een container staat de toewijzing van geïmplementeerde afhankelijkheden toe zonder dat dit van invloed is op de klasse.
- Een container vereenvoudigt de testbaarheid doordat afhankelijkheden kunnen worden gesimuleerd.
- Een container verhoogt de onderhoudbaarheid doordat nieuwe klassen eenvoudig aan de app kunnen worden toegevoegd.
In de context van een .NET-app MAUI die gebruikmaakt van MVVM, wordt doorgaans een container voor afhankelijkheidsinjectie gebruikt voor het registreren en oplossen van weergavemodellen, het registreren en oplossen van weergavemodellen, en voor het registreren van services en het injecteren ervan in weergavemodellen.
Er zijn veel containers voor afhankelijkheidsinjectie beschikbaar in .NET; de eShop-app voor meerdere platforms gebruikt Microsoft.Extensions.DependencyInjection
voor het beheren van de instantiëring van weergaven, het weergeven van modellen en serviceklassen in de app. Microsoft.Extensions.DependencyInjection
vereenvoudigt het bouwen van losjes gekoppelde apps en biedt alle functies die vaak voorkomen in containers voor afhankelijkheidsinjectie, waaronder methoden voor het registreren van typetoewijzingen en objectexemplaren, het omzetten van objecten, het beheren van de levensduur van objecten en het injecteren van afhankelijke objecten in constructors van objecten die worden omgezet. Zie Afhankelijkheidsinjectie in .NET voor meer informatie.Microsoft.Extensions.DependencyInjection
In .NET MAUIroept de MauiProgram
klasse de CreateMauiApp
methode aan om een MauiAppBuilder
object te maken. Het MauiAppBuilder
object heeft een Services
eigenschap van het type IServiceCollection
, die een plaats biedt om onze onderdelen te registreren, zoals weergaven, weergavemodellen en services voor afhankelijkheidsinjectie. Alle onderdelen die zijn geregistreerd bij de Services
eigenschap, worden geleverd aan de container voor afhankelijkheidsinjectie wanneer de MauiAppBuilder.Build
methode wordt aangeroepen.
Tijdens runtime moet de container weten welke implementatie van de services wordt aangevraagd om ze te instantiëren voor de aangevraagde objecten. In de eShop-app met meerdere platforms moeten de IAppEnvironmentService
, IDialogService
en INavigationService
ISettingsService
interfaces worden opgelost voordat een ProfileViewModel
object kan worden geïnstitueren. Dit omvat de container die de volgende acties uitvoert:
- Bepalen hoe u een object instantiëren waarmee de interface wordt geïmplementeerd. Dit staat bekend als registratie.
- Instantieer het object dat de vereiste interface en het
ProfileViewModel
object implementeert. Dit wordt resolutie genoemd.
Uiteindelijk wordt de app klaar met het gebruik van het ProfileViewModel
object en wordt deze beschikbaar voor garbagecollection. Op dit moment moet de garbagecollector eventuele kortdurende interface-implementaties verwijderen als andere klassen niet hetzelfde exemplaar delen.
Registratie
Voordat afhankelijkheden in een object kunnen worden geïnjecteerd, moeten de typen afhankelijkheden eerst worden geregistreerd bij de container. Het registreren van een type omvat het doorgeven van de container een interface en een concreet type dat de interface implementeert.
Er zijn twee manieren om typen en objecten in de container te registreren via code:
- Registreer een type of toewijzing bij de container. Dit wordt tijdelijke registratie genoemd. Indien nodig bouwt de container een exemplaar van het opgegeven type.
- Registreer een bestaand object in de container als een singleton. Indien nodig retourneert de container een verwijzing naar het bestaande object.
Notitie
Containers voor afhankelijkheidsinjectie zijn niet altijd geschikt. Afhankelijkheidsinjectie introduceert extra complexiteit en vereisten die mogelijk niet geschikt of nuttig zijn voor kleine apps. Als een klasse geen afhankelijkheden heeft of geen afhankelijkheid is voor andere typen, is het mogelijk niet zinvol om deze in de container te plaatsen. Als een klasse bovendien één set afhankelijkheden heeft die integraal zijn voor het type en nooit wordt gewijzigd, is het mogelijk niet zinvol om deze in de container te plaatsen.
De registratie van typen waarvoor afhankelijkheidsinjectie is vereist, moet worden uitgevoerd in één methode in een app. Deze methode moet vroeg in de levenscyclus van de app worden aangeroepen om ervoor te zorgen dat deze op de hoogte is van de afhankelijkheden tussen de klassen. De eShop-app voor meerdere platforms voert deze methode MauiProgram.CreateMauiApp
uit. In het volgende codevoorbeeld ziet u hoe de eShop-app met meerdere platforms de CreateMauiApp
app in de MauiProgram
klasse declareert:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<App>()
// Omitted for brevity
.RegisterAppServices()
.RegisterViewModels()
.RegisterViews()
.Build();
}
De MauiApp.CreateBuilder
methode maakt een MauiAppBuilder
object dat we kunnen gebruiken om onze afhankelijkheden te registreren. Veel afhankelijkheden in de eShop-app voor meerdere platforms moeten worden geregistreerd, dus de extensiemethoden RegisterAppServices
en RegisterViewModels
RegisterViews
zijn gemaakt om een georganiseerde en onderhoudbare registratiewerkstroom te bieden. De volgende code toont de RegisterViewModels
methode:
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;
}
Deze methode ontvangt een exemplaar van MauiAppBuilder
, en we kunnen de Services
eigenschap gebruiken om onze weergavemodellen te registreren. Afhankelijk van de behoeften van uw toepassing moet u mogelijk services met verschillende levensduur toevoegen. De volgende tabel bevat informatie over wanneer u deze verschillende registratielevensduur wilt kiezen:
Wijze | Description |
---|---|
AddSingleton<T> |
Er wordt één exemplaar van het object gemaakt dat gedurende de levensduur van de toepassing blijft bestaan. |
AddTransient<T> |
Er wordt een nieuw exemplaar van het object gemaakt wanneer dit wordt aangevraagd tijdens de oplossing. Tijdelijke objecten hebben geen vooraf gedefinieerde levensduur, maar volgen doorgaans de levensduur van hun host. |
Notitie
De weergavemodellen nemen niet over van een interface, dus ze hebben alleen hun concrete type nodig voor de AddSingleton<T>
en AddTransient<T>
methoden.
De CatalogViewModel
wordt gebruikt in de buurt van de hoofdmap van de toepassing en moet altijd beschikbaar zijn, dus het registreren bij AddSingleton<T>
is nuttig. Andere weergavemodellen, zoals CheckoutViewModel
en OrderDetailViewModel
die in de situatie worden genavigeerd of later in de toepassing worden gebruikt. Stel dat u weet dat u een onderdeel hebt dat mogelijk niet altijd wordt gebruikt. In dat geval is het geheugen of rekenintensief of vereist Just-In-Time-gegevens, dan is het mogelijk een betere kandidaat voor AddTransient<T>
registratie.
Een andere veelgebruikte manier om services toe te voegen, is het gebruik van de AddSingleton<TService, TImplementation>
en AddTransient<TService, TImplementation>
methoden. Deze methoden hebben twee invoertypen: de interfacedefinitie en de concrete implementatie. Dit type registratie is het meest geschikt voor gevallen waarin u services implementeert op basis van interfaces. In het onderstaande codevoorbeeld registreren we onze ISettingsService
interface met behulp van de SettingsService
implementatie:
public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
// Omitted for brevity...
}
Zodra alle services zijn geregistreerd, moet de MauiAppBuilder.Build
methode worden aangeroepen om onze MauiApp
container voor afhankelijkheidsinjectie te maken en te vullen met alle geregistreerde services.
Belangrijk
Zodra de Build
methode is aangeroepen, is de container voor afhankelijkheidsinjectie onveranderbaar en kan deze niet meer worden bijgewerkt of gewijzigd. Zorg ervoor dat alle services die u nodig hebt in uw toepassing zijn geregistreerd voordat u belt Build
.
Oplossing
Nadat een type is geregistreerd, kan het worden omgezet of geïnjecteerd als een afhankelijkheid. Wanneer een type wordt omgezet en de container een nieuw exemplaar moet maken, worden eventuele afhankelijkheden in het exemplaar ingevoerd.
Over het algemeen gebeurt er een van de volgende drie dingen wanneer een type is opgelost:
- Als het type niet is geregistreerd, genereert de container een uitzondering.
- Als het type is geregistreerd als een singleton, retourneert de container het singleton-exemplaar. Als dit de eerste keer is dat het type wordt aangeroepen, maakt de container deze indien nodig en onderhoudt de container een verwijzing naar het type.
- Als het type is geregistreerd als tijdelijk, retourneert de container een nieuw exemplaar en wordt er geen verwijzing naar onderhouden.
.NET MAUI biedt een aantal manieren om geregistreerde onderdelen op basis van uw behoeften op te lossen. De meest directe manier om toegang te krijgen tot de container voor afhankelijkheidsinjectie is van een Element
gebruik van de Handler.MauiContext.Services
. Hieronder ziet u een voorbeeld:
var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();
Dit kan handig zijn als u een service wilt oplossen van binnen of Element
buiten de constructor van uw Element
.
Let op
Er is een mogelijkheid dat de Handler
eigenschap van uw Element
waarde null is, dus houd er rekening mee dat u deze situaties moet afhandelen. Raadpleeg de levenscyclus van Handler in het Microsoft Documentation Center voor meer informatie.
Als u het Shell
besturingselement voor .NET MAUIgebruikt, wordt impliciet de afhankelijkheidsinjectiecontainer aangeroepen om onze objecten te maken tijdens de navigatie. Bij het instellen van onze Shell
controle wordt met de Routing.RegisterRoute
methode een routepad gekoppeld aan een View
pad zoals wordt weergegeven in het onderstaande voorbeeld:
Routing.RegisterRoute("Filter", typeof(FiltersView));
Tijdens Shell
de navigatie wordt gezocht naar registraties van de FiltersView
en indien aanwezig, wordt die weergave gemaakt en worden eventuele afhankelijkheden in de constructor ingevoerd. Zoals wordt weergegeven in het onderstaande codevoorbeeld, wordt de CatalogViewModel
in het FiltersView
volgende voorbeeld opgenomen:
namespace eShop.Views;
public partial class FiltersView : ContentPage
{
public FiltersView(CatalogViewModel viewModel)
{
BindingContext = viewModel;
InitializeComponent();
}
}
Tip
De container voor afhankelijkheidsinjectie is ideaal voor het maken van weergavemodelexemplaren. Als een weergavemodel afhankelijkheden heeft, wordt het maken en injecteren van vereiste services afgehandeld. Zorg ervoor dat u uw weergavemodellen en eventuele afhankelijkheden die ze mogelijk hebben met de CreateMauiApp
methode in de MauiProgram
klasse registreren.
Samenvatting
Met afhankelijkheidsinjectie kunt u betontypen loskoppelen van de code die afhankelijk is van deze typen. Doorgaans wordt een container gebruikt met een lijst met registraties en toewijzingen tussen interfaces en abstracte typen, en de concrete typen die deze typen implementeren of uitbreiden.
Microsoft.Extensions.DependencyInjection
vereenvoudigt het bouwen van losjes gekoppelde apps en biedt alle functies die vaak voorkomen in containers voor afhankelijkheidsinjectie, waaronder methoden voor het registreren van typetoewijzingen en objectexemplaren, het omzetten van objecten, het beheren van de levensduur van objecten en het injecteren van afhankelijke objecten in constructors van objecten die worden omgezet.