.NET MAUI Dependency Injection útmutató: szolgáltatások, élettartamok és tesztelés (2026)

Gyakorlati .NET MAUI Dependency Injection útmutató .NET 10-re: szolgáltatás regisztráció a MauiProgram.cs-ben, Singleton vs Transient élettartamok, Shell-integráció, HttpClient factory és tesztelhető ViewModelek konstruktor-injektálással.

.NET MAUI Dependency Injection Útmutató 2026

Frissítve: 2026. május 30.

A .NET MAUI Dependency Injection (DI) egy beépített tervezési minta, amely a Microsoft.Extensions.DependencyInjection konténerrel kezeli az alkalmazás osztályainak függőségeit a MauiProgram.cs fájlban regisztrált szolgáltatásokon keresztül. Ez teszi lehetővé, hogy a ViewModelek, oldalak és háttérszolgáltatások (HttpClient, naplózás, beállítások) tiszta konstruktor-injektálással kapják meg, amire szükségük van, anélkül, hogy a kódunk merev statikus hivatkozásokkal csatolódna össze. Ebben az útmutatóban végigvesszük a regisztrálást, az élettartamokat, a Shell-integrációt és a teszthelyzeteket gyakorlati .NET 10 példákkal.

  • A .NET MAUI a Microsoft.Extensions.DependencyInjection konténert használja, ugyanazt, amit az ASP.NET Core. A MauiAppBuilder.Services egy szabványos IServiceCollection.
  • Három élettartam létezik: Singleton (egy példány az alkalmazás teljes életciklusára), Scoped (ritkán használt MAUI-ban), és Transient (új példány minden feloldáskor).
  • Az oldalakat és ViewModeleket regisztrálni kell a konténerben, hogy a Shell és a konstruktor-injektálás működjön, különben InvalidOperationException-t kapunk indításkor.
  • A Singleton-ok és UI elemek (Page, ViewModel) keverése a leggyakoribb memóriaszivárgási forrás. A hosszú élettartamú szolgáltatások nem hivatkozhatnak rövid élettartamúakra.
  • A HttpClient-et mindig IHttpClientFactory-val regisztráljuk, ne AddSingleton<HttpClient>-tal, különben DNS-cache problémák jönnek elő.
  • Teszteléshez használjunk interfészeket és Moq-ot vagy NSubstitute-ot. A DI-konténer egy unit teszt számára nem szükséges.

Mi az a Dependency Injection a .NET MAUI-ban?

A Dependency Injection (DI) egy inversion-of-control technika, ahol egy objektum a függőségeit (más szolgáltatásokat) nem maga példányosítja, hanem kívülről kapja meg, leggyakrabban a konstruktorán keresztül. A .NET MAUI az első verziójától kezdve a Microsoft.Extensions.DependencyInjection NuGet csomagot használja konténerként, ami pontosan ugyanaz az implementáció, mint az ASP.NET Core mögött. Ez azt jelenti, hogy ha valaha is dolgoztál ASP.NET Core API-val, a MAUI DI mentális modellje azonnal ismerős lesz.

A gyakorlatban ez a következőképpen néz ki: minden funkciónk, ami egy ViewModelben, oldalon vagy háttérosztályban él, regisztrált szolgáltatásként megkaphatja az alkalmazás többi részét. Egy UserProfileViewModel nem hozza létre saját maga az HttpClient-et vagy az ILogger-t. Ezeket a konstruktora paraméterként kéri, és a MAUI futási idő a DI-konténerből oldja fel őket.

Mielőtt belevágnánk, érdemes megnézned a mi CommunityToolkit.Mvvm útmutatónkat is, mert az MVVM forrásgenerátorok és a DI együtt adják a modern MAUI app gerincét.

Hogyan regisztráljunk szolgáltatásokat a MauiProgram.cs-ben?

Minden DI regisztráció a MauiProgram.CreateMauiApp() metódusban történik. A builder.Services egy IServiceCollection, és ugyanazokat a kiterjesztő metódusokat használjuk, mint az ASP.NET Core-ban: AddSingleton, AddTransient, és (ritkábban) AddScoped. Az alábbi minta egy tipikus startup-fájlt mutat be három rétegnyi regisztrációval: infrastruktúra, üzleti szolgáltatások, és UI komponensek.

using Microsoft.Extensions.Logging;
using CommunityToolkit.Maui;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // 1. Infrastruktura szolgaltatasok (singleton, mert allapot nelkuliek)
        builder.Services.AddSingleton<IPreferences>(Preferences.Default);
        builder.Services.AddSingleton<IConnectivity>(Connectivity.Current);
        builder.Services.AddSingleton<IFileSystem>(FileSystem.Current);

        // 2. HTTP es REST kliens
        builder.Services.AddHttpClient<IApiClient, ApiClient>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/v1/");
            client.Timeout = TimeSpan.FromSeconds(15);
        });

        // 3. Uzleti szolgaltatasok
        builder.Services.AddSingleton<IUserRepository, UserRepository>();
        builder.Services.AddTransient<IOrderService, OrderService>();

        // 4. ViewModelek (transient, hogy minden navigacional friss allapot legyen)
        builder.Services.AddTransient<LoginViewModel>();
        builder.Services.AddTransient<UserProfileViewModel>();
        builder.Services.AddTransient<OrderListViewModel>();

        // 5. Oldalak (a Shell ezekbol old fel navigaciokor)
        builder.Services.AddTransient<LoginPage>();
        builder.Services.AddTransient<UserProfilePage>();
        builder.Services.AddTransient<OrderListPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif
        return builder.Build();
    }
}

Vegyük észre a sorrendet: először az infrastruktúrát regisztráljuk, mert ezektől függenek az üzleti szolgáltatások, amelyektől viszont a ViewModelek függenek. A konténer maga megoldja a feloldási sorrendet, de logikailag könnyebb így olvasni, és code review során is gyorsabb átlátni. A hivatalos .NET MAUI architektúra-útmutatóban a Microsoft is ezt a struktúrát ajánlja.

Service lifetimes: Singleton, Scoped és Transient

A DI konténer három élettartamot támogat, de a MAUI mobil kontextus alapvetően másképp viselkedik, mint az ASP.NET Core webszerver. Egy webszerveren a Scoped élettartam HTTP kérésenként új példányt jelent, mobilon viszont nincs „request" fogalom, így a Scoped a gyakorlatban majdnem soha nem hasznos egy szabványos MAUI alkalmazásban. Maradunk a Singleton és a Transient mellett.

ÉlettartamMikor használjukPéldákVeszélyek
SingletonÁllapot nélküli vagy globális állapotú szolgáltatásokIPreferences, IUserRepository, IConnectivityNem hivatkozhat Transient függőségre
TransientMinden feloldáskor friss állapot kellViewModelek, oldalak, rövid életű parancsokMemóriafogyasztás, ha nem dispose-oljuk
ScopedRitkán használt MAUI-banEgyedi IServiceScope-on belüli munkamenetImplicit scope nincs, manuálisan kell létrehozni

A leggyakoribb hiba, amit code review során látunk: valaki egy Singleton szolgáltatásba beinjekciózik egy Transient ViewModelt vagy oldalt. Ez az ún. „captive dependency" probléma. A Singleton megtartja a referenciát, így a Transient élettartama de facto Singleton lesz. Memóriaszivárgáshoz vezet, és a UI-állapot „beragad". Egyik utolsó projektünkben pont ezzel találkoztam: a profil képernyő tartalma nem akart frissülni navigáció után, és csak miután megnéztem a regisztrációkat, lett nyilvánvaló, hogy egy logger-szerű singleton-on keresztül lóg bent a ViewModel.

Hogyan injektáljunk szolgáltatásokat ViewModelekbe?

A konstruktor-injektálás a MAUI standard mintája. A ViewModel a paraméterek listájában kéri, amire szüksége van, és a konténer feloldja őket. A CommunityToolkit.Mvvm ObservableObject bázisosztálya és a forrásgenerátorok tökéletesen együttműködnek a DI-vel.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;

public partial class UserProfileViewModel : ObservableObject
{
    private readonly IApiClient _apiClient;
    private readonly IUserRepository _userRepository;
    private readonly ILogger<UserProfileViewModel> _logger;

    [ObservableProperty]
    private string? _displayName;

    [ObservableProperty]
    private bool _isBusy;

    public UserProfileViewModel(
        IApiClient apiClient,
        IUserRepository userRepository,
        ILogger<UserProfileViewModel> logger)
    {
        _apiClient = apiClient;
        _userRepository = userRepository;
        _logger = logger;
    }

    [RelayCommand]
    private async Task LoadProfileAsync()
    {
        if (IsBusy) return;
        try
        {
            IsBusy = true;
            var profile = await _apiClient.GetUserProfileAsync();
            DisplayName = profile.Name;
            await _userRepository.SaveAsync(profile);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Profil betoltese sikertelen");
        }
        finally
        {
            IsBusy = false;
        }
    }
}

Vegyük észre: a ViewModel semmit sem tud arról, hogy az IApiClient mögött valójában egy HttpClient-alapú implementáció áll. Cserélhetnénk a teljes hálózati réteget egy in-memory mockra a teszteknél anélkül, hogy a ViewModelhez hozzá kellene nyúlnunk. Ez a DI valódi értéke. Ha REST hívásokkal dolgozol, a .NET MAUI HttpClient útmutatónk mélyebben tárgyalja az IApiClient tervezési mintát.

Page DI Shell navigációval

A .NET MAUI Shell automatikusan használja a DI konténert oldal- és ViewModel-feloldáshoz, ha a Shell Routes-aiban regisztrált útvonalra navigálsz. A trükk az, hogy mind az oldalt, mind a ViewModelt regisztrálni kell a konténerben, és az oldal BindingContext-jét konstruktoron keresztül beállítani.

// UserProfilePage.xaml.cs
public partial class UserProfilePage : ContentPage
{
    public UserProfilePage(UserProfileViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

A Shell GoToAsync hívás automatikusan a konténerből kéri ki az oldalt. Ha az oldal vagy a ViewModel nincs regisztrálva, InvalidOperationException: Unable to resolve service hibát kapsz. Ez a leggyakoribb indítási hiba, amit junior MAUI fejlesztők elkövetnek. A Shell navigációs útmutatónk részletesen bemutatja az útvonalak regisztrálását.

Paraméterek átadásánál ne a konstruktorba próbáljuk őket beinjektálni, erre van a QueryProperty attribútum vagy az IQueryAttributable interfész. A konstruktor csak DI-szolgáltatásokat kaphat.

Constructor injection vs. Service Locator

Néha kísértésbe esik az ember, hogy az IPlatformApplication.Current.Services.GetRequiredService<T>() hívással bárhonnan kihúzzon egy szolgáltatást a konténerből. Ez a Service Locator minta, és anti-pattern. Elrejti a függőségeket, megnehezíti a tesztelést, és futási idejű hibákat okoz olyan dolgokra, amik fordításkor kiderülhetnének.

Ennek ellenére van három legitim eset, ahol használjuk:

  1. Statikus konverterek vagy markup extensions, ahol nincs konstruktor.
  2. App.xaml.cs vagy más bootstrap kód, ahol a DI még éppen most konfigurálódott.
  3. Platform-specifikus handler vagy lifecycle event, ahol a MAUI futási idő ad át objektumokat.
// Csak ha tenyleg muszaj, pl. egy MarkupExtension-ben
public class LocalizedStringExtension : IMarkupExtension<string>
{
    public string Key { get; set; } = string.Empty;

    public string ProvideValue(IServiceProvider serviceProvider)
    {
        var localizer = IPlatformApplication.Current!
            .Services.GetRequiredService<ILocalizationService>();
        return localizer.Get(Key);
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
        => ProvideValue(serviceProvider);
}

Minden más esetben konstruktor-injektálást használj. A Microsoft DI guideline dokumentációja részletesen indokolja, miért rossz általában a Service Locator.

HttpClient regisztrálása IHttpClientFactory-val

A HttpClient-et soha ne regisztráld AddSingleton<HttpClient>-tal és soha ne példányosítsd ViewModelben. Honestly, ezt a hibát elkövettem én is anno: egy hosszú élettartamú HttpClient elavult DNS bejegyzéseket cache-el (mobilon hálózatváltáskor, Wi-Fi-ről mobil adatra, ez komoly probléma), míg a túl gyakran létrehozott HttpClient socket-kifulladást (SocketException) okoz. A megoldás az IHttpClientFactory.

// MauiProgram.cs-ben
builder.Services.AddHttpClient<IApiClient, ApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com/v1/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(20);
})
.AddStandardResilienceHandler(); // .NET 8+ retry, timeout, circuit breaker

// Hasznalat a tipusolt kliensben
public class ApiClient : IApiClient
{
    private readonly HttpClient _http;

    public ApiClient(HttpClient http) => _http = http;

    public async Task<UserProfile> GetUserProfileAsync()
    {
        var response = await _http.GetFromJsonAsync<UserProfile>("me");
        return response ?? throw new InvalidOperationException("Ures valasz");
    }
}

Az AddStandardResilienceHandler() a Polly-alapú újrapróbálási, timeout és circuit-breaker viselkedést adja hozzá egy sorral. A mi tapasztalatunk szerint: ha kihagyod, az első instabil hálózatú felhasználói visszajelzéseknél vissza fogod tenni. Tényleg.

Tesztelhetőség: mockolás és unit teszt minták

A DI legnagyobb gyakorlati haszna nem az architektúrai szépség, hanem a tesztelhetőség. Egy konstruktor-injektált ViewModelt mocked függőségekkel pillanatok alatt tesztelhetünk, és a DI-konténer maga teljesen kimarad az unit tesztből. Az alábbi példa xUnit + NSubstitute kombinációt használ, ami a csapatomban a standard.

using NSubstitute;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

public class UserProfileViewModelTests
{
    [Fact]
    public async Task LoadProfile_SikeresValasz_BeallitjaADisplayNeve()
    {
        // Arrange
        var apiClient = Substitute.For<IApiClient>();
        var userRepo = Substitute.For<IUserRepository>();
        apiClient.GetUserProfileAsync()
            .Returns(new UserProfile { Name = "Kovacs Anna" });

        var sut = new UserProfileViewModel(
            apiClient,
            userRepo,
            NullLogger<UserProfileViewModel>.Instance);

        // Act
        await sut.LoadProfileCommand.ExecuteAsync(null);

        // Assert
        Assert.Equal("Kovacs Anna", sut.DisplayName);
        Assert.False(sut.IsBusy);
        await userRepo.Received(1).SaveAsync(Arg.Any<UserProfile>());
    }
}

Vegyük észre: nincs MauiApp, nincs ServiceProvider, nincs platform-specifikus inicializálás. Egy egyszerű .NET osztálykönyvtárban fut, milliszekundumok alatt. Ez akkor lehetséges, ha minden függőség interfész mögött van. Ez a fegyelem hozza a DI valódi nyereségét.

Gyakori hibák és teljesítménytippek

A produkciós tapasztalatunk alapján öt hiba ismétlődik a leggyakrabban DI-vel dolgozó MAUI projektekben, érdemes előre tudni róluk.

1. Elfelejtett oldal- vagy ViewModel-regisztráció

Hibaüzenet: Unable to resolve service for type 'MyApp.OrderListViewModel'. Mindig regisztráld mind az oldalt, mind a ViewModelt a MauiProgram.cs-ben.

2. Singleton ViewModel

Tünet: navigáció után a régi adatok láthatók. Megoldás: ViewModel-ek mindig AddTransient.

3. Captive dependency

Tünet: emelkedő memóriahasználat, ViewModelek nem GC-zelődnek. Soha ne injektálj Transient szolgáltatást Singleton-ba közvetlenül. Helyette használj IServiceProvider-t és scope-ot, ha tényleg muszáj.

4. Indítási idő

Minden AddSingleton az alkalmazás teljes futási ideje alatt él, de a regisztráció maga lusta: a példány csak az első feloldáskor jön létre. Ha nehéz inicializáció van valami szolgáltatásban (pl. SQLite kapcsolat), érdemes AsyncLazy<T>-be csomagolni. Részletesebben a SQLite helyi adatbázis útmutatónkban tárgyaljuk az inicializálási stratégiákat.

5. Generikus szolgáltatások

A nyitott generikusok regisztrálása kicsit más szintaxis: builder.Services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));. A szögletes zárójelek közt semmi nincs, ez nem elgépelés.

Gyakran Ismételt Kérdések

Mi a különbség a Singleton és a Transient között .NET MAUI-ban?

A Singleton egy példányt hoz létre az alkalmazás teljes életciklusára, és minden injektálási helyen ugyanazt a példányt adja vissza. A Transient minden feloldáskor új példányt készít. MAUI-ban a háttérszolgáltatásokat (repo, API kliens) Singleton-ként, a ViewModeleket és oldalakat Transient-ként regisztráljuk.

Miért kapok „Unable to resolve service" hibát .NET MAUI-ban?

Mert az oldal vagy ViewModel nincs regisztrálva a MauiProgram.cs-ben. Add hozzá builder.Services.AddTransient<YourPage>() és builder.Services.AddTransient<YourViewModel>() sorokkal. A Shell navigáció a DI-konténerből oldja fel az oldalakat, így a regisztráció kötelező.

Használhatok Scoped élettartamot .NET MAUI-ban?

Technikailag igen, de a gyakorlatban majdnem soha nem hasznos. A MAUI-ban nincs ASP.NET Core-szerű implicit „request scope", így a Scoped szolgáltatás úgy viselkedik, mint a Transient, kivéve, ha manuálisan hozol létre IServiceScope-ot. Maradj a Singleton és Transient mellett.

Hogyan injektáljam a HttpClient-et MAUI-ban?

Ne közvetlenül. Használj AddHttpClient<IApiClient, ApiClient>()-t típusolt klienssel. Ez integrálja az IHttpClientFactory-t, kezeli a DNS cache-t, a socket élettartamot, és lehetővé teszi a Polly-alapú resilience handler hozzáadását egy sorral.

Kell-e DI konténer az unit tesztekhez?

Nem. Egy unit teszt csak a tesztelt osztály konstruktorát hívja, és kézzel ad át mock függőségeket. A DI konténer csak az alkalmazás futási idejének része, tesztben nincs szükség MauiApp-ra vagy ServiceProvider-re. Integrációs tesztben ez más, de unit tesztben felesleges komplexitás.

Priya Sharma
A Szerzőről Priya Sharma

Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.