Sdílet prostřednictvím


Injektáž závislostí

Rozhraní .NET Multi-Platform App UI (.NET MAUI) poskytuje integrovanou podporu pro použití injektáže závislostí. Injektáž závislostí je specializovaná verze modelu Inversion of Control (IoC), kde problém invertovaný je proces získání požadované závislosti. Při injektáži závislostí zodpovídá jiná třída za vkládání závislostí do objektu za běhu.

Konstruktor třídy se obvykle vyvolá při vytváření instance objektu a všechny hodnoty, které objekt potřebuje, jsou předány jako argumenty konstruktoru. Toto je příklad injektáž závislostí označované jako injektáž konstruktoru. Závislosti, které objekt potřebuje, se vloží do konstruktoru.

Poznámka:

Existují také další typy injektáže závislostí, jako je injektáž setter vlastností a injektáž volání metody, ale méně často se používají.

Zadáním závislostí jako typů rozhraní umožňuje injektáž závislostí oddělit konkrétní typy od kódu, který závisí na těchto typech. Obvykle používá kontejner, který obsahuje seznam registrací a mapování mezi rozhraními a abstraktními typy a konkrétní typy, které implementují nebo rozšiřují tyto typy.

Kontejnery injektáže závislostí

Pokud třída přímo vytvoří instanci objektů, které potřebuje, musí tuto odpovědnost převzít jiná třída. Podívejte se na následující příklad, který ukazuje třídu modelu zobrazení, která vyžaduje argumenty konstruktoru:

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
    {
        _loggingService = loggingService;
        _settingsService = settingsService;
    }
}

V tomto příkladu MainPageViewModel konstruktor vyžaduje dvě instance objektu rozhraní jako argumenty vložené jinou třídou. Jediná závislost ve MainPageViewModel třídě je na typech rozhraní. MainPageViewModel Třída proto nemá žádné znalosti třídy, která je zodpovědná za vytvoření instance objektů rozhraní.

Podobně zvažte následující příklad, který ukazuje třídu stránky, která vyžaduje argument konstruktoru:

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

V tomto příkladu MainPage konstruktor vyžaduje konkrétní typ jako argument vložený jinou třídou. Jediná závislost ve MainPage třídě je na MainPageViewModel typu. MainPage Třída proto nemá žádné znalosti třídy, která je zodpovědná za vytvoření instance konkrétního typu.

V obou případech se třída, která je zodpovědná za vytvoření instance závislostí, a jejich vložení do závislé třídy, označuje jako kontejner injektáž závislostí.

Kontejnery injektáže závislostí snižují spojení mezi objekty tím, že poskytují zařízení pro vytvoření instance instancí tříd a správu jejich životnosti na základě konfigurace kontejneru. Během vytváření objektu kontejner vloží všechny závislosti, které objekt vyžaduje. Pokud se tyto závislosti nevytvořily, kontejner nejprve vytvoří a přeloží jejich závislosti.

Použití kontejneru injektáže závislostí má několik výhod:

  • Kontejner eliminuje potřebu třídy vyhledat její závislosti a spravovat jeho životnost.
  • Kontejner umožňuje mapování implementovaných závislostí, aniž by to ovlivnilo třídu.
  • Kontejner usnadňuje testovatelnost tím, že umožňuje napodobení závislostí.
  • Kontejner zvyšuje udržovatelnost tím, že umožňuje snadné přidání nových tříd do aplikace.

V kontextu aplikace .NET MAUI, která používá model model-View-ViewModel (MVVM), se kontejner injektáže závislostí obvykle použije k registraci a překladu zobrazení, registraci a překladu modelů zobrazení a k registraci služeb a jejich vkládání do modelů zobrazení. Další informace o modelu MVVM naleznete v tématu Model-View-ViewModel (MVVM).

Pro .NET je k dispozici mnoho kontejnerů injektáže závislostí. .NET MAUI má integrovanou podporu pro správu Microsoft.Extensions.DependencyInjection vytváření instancí zobrazení, zobrazení modelů a tříd služeb v aplikaci. Microsoft.Extensions.DependencyInjection usnadňuje vytváření volně propojených aplikací a poskytuje všechny funkce, které se běžně vyskytují v kontejnerech injektáže závislostí, včetně metod registrace mapování typů a instancí objektů, překladu objektů, správy životností objektů a vkládání závislých objektů do konstruktorů objektů, které řeší. Další informace o Microsoft.Extensions.DependencyInjectioninjektáži závislostí v .NET.

Za běhu musí kontejner vědět, jaká implementace závislostí se požaduje, aby bylo možné vytvořit instanci požadovaných objektů. V předchozím příkladu je potřeba před ILoggingService vytvořením instance objektu MainPageViewModel vyřešit rozhraní a ISettingsService rozhraní. To zahrnuje kontejner, který provádí následující akce:

  • Rozhodování o vytvoření instance objektu, který implementuje rozhraní. To se označuje jako registrace. Další informace najdete v tématu Registrace.
  • Vytvoření instance objektu, který implementuje požadované rozhraní a MainPageViewModel objekt. To se označuje jako řešení. Další informace najdete v tématu Řešení.

Nakonec se aplikace dokončí s použitím objektu MainPageViewModel a zpřístupní se pro uvolňování paměti. V tomto okamžiku by systém uvolňování paměti měl likvidovat jakékoli krátkodobé implementace rozhraní, pokud jiné třídy nesdílely stejné instance.

Registrace

Před vložením závislostí do objektu je nutné nejprve zaregistrovat typy závislostí v kontejneru. Registrace typu obvykle zahrnuje předání kontejneru konkrétního typu nebo rozhraní a konkrétního typu, který implementuje rozhraní.

Existují dva hlavní přístupy k registraci typů a objektů v kontejneru:

  • Zaregistrujte typ nebo mapování v kontejneru. Tato registrace se označuje jako přechodná registrace. V případě potřeby kontejner sestaví instanci zadaného typu.
  • Zaregistrujte existující objekt v kontejneru jako singleton. V případě potřeby kontejner vrátí odkaz na existující objekt.

Upozornění

Kontejnery injektáže závislostí nejsou vždy vhodné pro aplikaci .NET MAUI. Injektáž závislostí přináší další složitost a požadavky, které nemusí být vhodné nebo užitečné pro menší aplikace. Pokud třída nemá žádné závislosti nebo není závislostí pro jiné typy, nemusí mít smysl ji vložit do kontejneru. Kromě toho, pokud třída má jednu sadu závislostí, které jsou integrální pro typ a nikdy se nezmění, nemusí mít smysl je vložit do kontejneru.

Registrace typů vyžadujících injektáž závislostí by se měla provádět v jedné metodě ve vaší aplikaci. Tato metoda by měla být vyvolána v rané fázi životního cyklu aplikace, aby se zajistilo, že bude vědět o závislostech mezi jeho třídami. Aplikace by to obvykle měly provádět v CreateMauiApp metodě ve MauiProgram třídě. Třída MauiProgram volá do CreateMauiApp metody k vytvoření objektu MauiAppBuilder . Objekt MauiAppBuilderServices vlastnost typu IServiceCollection, která poskytuje místo pro registraci typů, jako jsou zobrazení, modely zobrazení a služby pro injektáž závislostí:

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, které jsou registrovány s Services vlastností jsou poskytovány kontejneru injektáž závislostí při MauiAppBuilder.Build() volání.

Při registraci závislostí musíte zaregistrovat všechny závislosti, včetně všech typů, které vyžadují závislosti. Proto pokud máte model zobrazení, který přebírá závislost jako parametr konstruktoru, musíte zaregistrovat model zobrazení spolu se všemi jeho závislostmi. Podobně pokud máte zobrazení, které používá závislost modelu zobrazení jako parametr konstruktoru, musíte zobrazení zaregistrovat a model zobrazení spolu se všemi jeho závislostmi.

Tip

Kontejner injektáže závislostí je ideální pro vytváření instancí modelu zobrazení. Pokud model zobrazení obsahuje závislosti, bude spravovat vytváření a injektáž všech požadovaných služeb. Ujistěte se, že zaregistrujete modely zobrazení a všechny závislosti, které mohou mít v CreateMauiApp metodě ve MauiProgram třídě.

Životnost závislostí

V závislosti na potřebách vaší aplikace možná budete muset zaregistrovat závislosti s různými životnostmi. Následující tabulka uvádí hlavní metody, které můžete použít k registraci závislostí a jejich životnosti registrace:

metoda Popis
AddSingleton<T> Vytvoří jednu instanci objektu, která zůstane po celou dobu života aplikace.
AddTransient<T> Vytvoří novou instanci objektu při požadavku během řešení. Přechodné objekty nemají předdefinované životnosti, ale obvykle se řídí životností svého hostitele.
AddScoped<T> Vytvoří instanci objektu, který sdílí životnost svého hostitele. Když hostitel přejde mimo rozsah, tak i její závislost. Proto překlad stejné závislosti vícekrát ve stejném rozsahu vrátí stejnou instanci, zatímco překlad stejné závislosti v různých oborech přinese různé instance.

Poznámka:

Pokud objekt nedědí z rozhraní, jako je zobrazení nebo model zobrazení, musí být k dispozici AddSingleton<T>pouze jeho konkrétní typ , AddTransient<T>nebo AddScoped<T> metodu.

Třída MainPageViewModel se používá blízko kořenového adresáře aplikace a měla by být vždy dostupná, takže její AddSingleton<T> registrace je výhodná. Jiné modely zobrazení můžou být v aplikaci situované nebo se můžou použít později. Pokud máte typ, který nemusí být vždy použit, nebo pokud je paměť nebo výpočetně náročné nebo vyžaduje data za běhu, může být vhodnějším kandidátem na AddTransient<T> registraci.

Dalším běžným způsobem registrace závislostí je použití AddSingleton<TService, TImplementation>AddTransient<TService, TImplementation>, nebo AddScoped<TService, TImplementation> metod. Tyto metody mají dva typy – definici rozhraní a konkrétní implementaci. Tento typ registrace je nejvhodnější pro případy, kdy implementujete služby založené na rozhraních.

Po registraci všech typů by se měly volat k MauiAppBuilder.Build() vytvoření objektu MauiApp a naplnění kontejneru injektáže závislostí všemi registrovanými typy.

Důležité

Po MauiAppBuilder.Build() zavolání budou typy zaregistrované v kontejneru injektáže závislostí neměnné a už není možné je aktualizovat ani upravit.

Registrace závislostí pomocí metody rozšíření

Metoda MauiApp.CreateBuilder vytvoří MauiAppBuilder objekt, který lze použít k registraci závislostí. Pokud vaše aplikace potřebuje registrovat mnoho závislostí, můžete vytvořit metody rozšíření, které vám pomůžou zajistit uspořádaný a udržovatelný pracovní postup registrace:

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;        
    }
}

V tomto příkladu tři metody rozšíření registrace používají MauiAppBuilder instanci pro přístup Services k vlastnosti pro registraci závislostí.

Rozlišení

Jakmile je typ zaregistrovaný, lze ho vyřešit nebo vkládat jako závislost. Při překladu typu a kontejner musí vytvořit novou instanci, vloží do instance všechny závislosti.

Obecně platí, že když se typ vyřeší, stane se jeden ze tří scénářů:

  1. Pokud typ není zaregistrovaný, kontejner vyvolá výjimku.
  2. Pokud byl typ registrován jako singleton, vrátí kontejner instanci singleton. Pokud se jedná o první volání typu, kontejner ho v případě potřeby vytvoří a udržuje odkaz na něj.
  3. Pokud byl typ registrován jako přechodný, kontejner vrátí novou instanci a neudržuje odkaz na něj.

.NET MAUI podporuje automatické a explicitní rozlišení závislostí. Automatické řešení závislostí používá injektáž konstruktoru bez explicitního vyžádání závislosti z kontejneru. Explicitní rozlišení závislostí probíhá na vyžádání explicitním vyžádáním závislosti z kontejneru.

Automatické řešení závislostí

Automatické řešení závislostí probíhá v aplikacích, které používají prostředí .NET MAUI Shell za předpokladu, že jste zaregistrovali typ závislosti a typ, který používá závislost s kontejnerem injektáže závislostí.

Během navigace založené na prostředí bude .NET MAUI hledat registrace stránek a pokud jsou nalezeny, vytvoří danou stránku a vloží všechny závislosti do svého konstruktoru:

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

V tomto příkladu MainPage konstruktor obdrží MainPageViewModel instanci, která je vložena. Instance má naopak MainPageViewModelILoggingService vložené instance a ISettingsService instance:

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(ILoggingService loggingService, ISettingsService settingsService)
    {
        _loggingService = loggingService;
        _settingsService = settingsService;
    }
}

Kromě toho v aplikaci založené na prostředí vloží rozhraní .NET MAUI závislosti na stránkách podrobností, které jsou zaregistrované v Routing.RegisterRoute metodě.

Explicitní rozlišení závislostí

Aplikace založená na prostředí nemůže použít injektáž konstruktoru, pokud typ zveřejňuje pouze konstruktor bez parametrů. Případně pokud vaše aplikace nepoužívá Shell, budete muset použít explicitní řešení závislostí.

Kontejner injektáže závislostí lze explicitně přistupovat z Element jeho Handler.MauiContext.Service vlastnosti, která je typu IServiceProvider:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        HandlerChanged += OnHandlerChanged;
    }

    void OnHandlerChanged(object sender, EventArgs e)
    {
        BindingContext = Handler.MauiContext.Services.GetService<MainPageViewModel>();
    }
}

Tento přístup může být užitečný, pokud potřebujete vyřešit závislost z objektu Elementnebo mimo konstruktor objektu Element. V tomto příkladu přístup ke kontejneru injektáže závislostí v HandlerChanged obslužné rutině události zajišťuje, že obslužná rutina byla nastavena pro stránku, a proto Handler vlastnost nebude null.

Upozorňující

Vlastnost Handler vašeho Element může být null, takže mějte na paměti, že možná budete muset zohlednit tuto situaci. Další informace naleznete v tématu Životní cyklus obslužné rutiny.

V modelu zobrazení je kontejner injektáž závislostí explicitně přístupný prostřednictvím Handler.MauiContext.Service vlastnosti 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>();
    }
}

Nevýhodou tohoto přístupu je, že model zobrazení teď má závislost na Application typu. Tuto nevýhodu však lze eliminovat předáním IServiceProvider argumentu konstruktoru modelu zobrazení. Řeší se IServiceProvider prostřednictvím automatického řešení závislostí, aniž byste ho museli zaregistrovat do kontejneru injektáže závislostí. Pomocí tohoto přístupu je možné typ a jeho IServiceProvider závislost automaticky vyřešit za předpokladu, že je typ registrován v kontejneru injektáž závislostí. Pak IServiceProvider se dá použít k explicitnímu řešení závislostí:

public class MainPageViewModel
{
    readonly ILoggingService _loggingService;
    readonly ISettingsService _settingsService;

    public MainPageViewModel(IServiceProvider serviceProvider)
    {
        _loggingService = serviceProvider.GetService<ILoggingService>();
        _settingsService = serviceProvider.GetService<ISettingsService>();
    }
}

Kromě toho je možné k IServiceProvider instanci přistupovat na každé platformě prostřednictvím IPlatformApplication.Current.Services vlastnosti.

Omezení prostředků XAML

Běžným scénářem je registrace stránky s kontejnerem injektáže závislostí a jeho vložení do konstruktoru App pomocí automatického MainPage řešení závislostí a jeho nastavení jako hodnoty vlastnosti:

public App(MyFirstAppPage page)
{
    InitializeComponent();
    MainPage = page;
}

Pokud se však v tomto scénáři MyFirstAppPage pokusí o přístup k deklarovanému StaticResource kódu XAML ve App slovníku prostředků, XamlParseException vyvolá se zpráva podobná Position {row}:{column}. StaticResource not found for key {key}. K tomu dochází, protože stránka vyřešená prostřednictvím injektáže konstruktoru byla vytvořena před inicializací prostředků XAML na úrovni aplikace.

Alternativním řešením tohoto problému je vložení do IServiceProvider třídy App a jeho následné použití k vyřešení stránky uvnitř App třídy:

public App(IServiceProvider serviceProvider)
{
    InitializeComponent();
    MainPage = serviceProvider.GetService<MyFirstAppPage>();
}

Tento přístup vynutí vytvoření a inicializaci stromu objektů XAML před vyřešením stránky.