Partilhar via


Injeção de dependência

A interface do usuário do aplicativo multiplataforma .NET (.NET MAUI) fornece suporte interno para o uso de injeção de dependência. A injeção de dependência é uma versão especializada do padrão de Inversão de Controle (IoC), onde a preocupação que está sendo invertida é o processo de obtenção da dependência necessária. Com a injeção de dependência, outra classe é responsável por injetar dependências em um objeto em runtime.

Normalmente, um construtor de classe é invocado ao instanciar um objeto e todos os valores necessários pelo objeto são passados como argumentos para o construtor. Este é um exemplo de injeção de dependência conhecida como injeção de construtor. As dependências de que o objeto precisa são injetadas no construtor.

Observação

Existem também outros tipos de injeção de dependência, como injeção de setter de propriedade e injeção de chamada de método, mas eles são menos comumente usados.

Ao especificar dependências como tipos de interface, a injeção de dependência permite desacoplar os tipos concretos do código que depende desses tipos. Geralmente, ele usa um contêiner que contém uma lista de registros e mapeamentos entre interfaces e tipos abstratos e os tipos concretos que implementam ou estendem esses tipos.

Contêineres de injeção de dependência

Se uma classe não instanciar diretamente os objetos de que precisa, outra classe deverá assumir essa responsabilidade. Considere o exemplo a seguir, que mostra uma classe view-model que requer argumentos do construtor:

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

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

Neste exemplo, o construtor requer duas instâncias de objeto de MainPageViewModel interface como argumentos injetados por outra classe. A única dependência na classe MainPageViewModel é nos tipos de interface. Portanto, a classe MainPageViewModel não tem nenhum conhecimento da classe responsável por instanciar os objetos de interface.

Da mesma forma, considere o exemplo a seguir que mostra uma classe de página que requer um argumento de construtor:

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

Neste exemplo, o MainPage construtor requer um tipo concreto como um argumento que é injetado por outra classe. A única dependência na MainPage classe é no MainPageViewModel tipo. Portanto, a MainPage classe não tem nenhum conhecimento da classe responsável por instanciar o tipo de concreto.

Em ambos os casos, a classe responsável por instanciar as dependências e inseri-las na classe dependente é conhecida como contêiner de injeção de dependência.

Os contêineres de injeção de dependência reduzem o acoplamento entre objetos fornecendo uma instalação para instanciar instâncias de classe e gerenciar seu tempo de vida com base na configuração do contêiner. Durante a criação do objeto, o contêiner injeta todas as dependências que o objeto requer. Se essas dependências não tiverem sido criadas, o contêiner criará e resolverá suas dependências primeiro.

Há várias vantagens em usar um contêiner de injeção de dependência:

  • Um contêiner remove a necessidade de uma classe localizar suas dependências e gerenciar seus tempos de vida.
  • Um contêiner permite o mapeamento de dependências implementadas sem afetar a classe.
  • Um contêiner facilita a capacidade de teste permitindo que as dependências sejam simuladas.
  • Um contêiner aumenta a capacidade de manutenção, permitindo que novas classes sejam facilmente adicionadas ao aplicativo.

No contexto de um aplicativo .NET MAUI que usa o padrão MVVM (Model-View-ViewModel), um contêiner de injeção de dependência normalmente será usado para registrar e resolver modos de exibição, registrar e resolver modelos de exibição e para registrar serviços e injetá-los em modelos de exibição. Para obter mais informações sobre o padrão MVVM, consulte Model-View-ViewModel (MVVM).

Há muitos contêineres de injeção de dependência disponíveis para .NET. O .NET MAUI tem suporte interno para uso Microsoft.Extensions.DependencyInjection para gerenciar a instanciação de modos de exibição, modelos de exibição e classes de serviço em um aplicativo. Microsoft.Extensions.DependencyInjection facilita a criação de aplicativos acoplados de forma flexível e fornece todos os recursos comumente encontrados em contêineres de injeção de dependência, incluindo métodos para registrar mapeamentos de tipo e instâncias de objeto, resolver objetos, gerenciar tempos de vida de objetos e injetar objetos dependentes em construtores de objetos que ele resolve. Para mais informações sobre Microsoft.Extensions.DependencyInjection confira Injeção de dependência no .NET.

Em tempo de execução, o contêiner deve saber qual implementação das dependências está sendo solicitada para instanciá-las para os objetos solicitados. No exemplo acima, as ILoggingService interfaces e ISettingsService precisam ser resolvidas antes que o MainPageViewModel objeto possa ser instanciado. Isso envolve o contêiner executando as seguintes ações:

  • Decidindo como instanciar um objeto que implementa a interface. Isso é conhecido como registro. Para saber mais, confira Registro.
  • Instanciando o objeto que implementa a interface necessária e o objeto MainPageViewModel. Isso é conhecido como resolução. Para obter mais informações, consulte Resolução.

Eventualmente, um aplicativo terminará de usar o objeto e ficará disponível para coleta de MainPageViewModel lixo. Neste ponto, o coletor de lixo deve descartar quaisquer implementações de interface de curta duração se outras classes não compartilharem as mesmas instâncias.

Registro

Antes que as dependências possam ser injetadas em um objeto, os tipos para as dependências devem primeiro ser registrados com o contêiner. O registro de um tipo normalmente envolve a passagem do contêiner para um tipo de concreto, ou uma interface e um tipo de concreto que implementa a interface.

Há duas abordagens principais para registrar tipos e objetos com o contêiner:

  • Registre um tipo ou mapeamento com o contêiner. Isso é conhecido como registro transitório. Quando necessário, o contêiner criará uma instância do tipo especificado.
  • Registre um objeto existente no contêiner como um singleton. Quando necessário, o contêiner retornará uma referência ao objeto existente.

Cuidado

Os contêineres de injeção de dependência nem sempre são adequados para um aplicativo .NET MAUI. A injeção de dependência introduz complexidade e requisitos adicionais que podem não ser apropriados ou úteis para aplicativos menores. Se uma classe não tiver dependências ou não for uma dependência para outros tipos, talvez não faça sentido colocá-la no contêiner. Além disso, se uma classe tiver um único conjunto de dependências que são parte integrante do tipo e nunca serão alteradas, talvez não faça sentido colocá-las no contêiner.

O registro de tipos que exigem injeção de dependência deve ser realizado em um único método em seu aplicativo. Esse método deve ser chamado no início do ciclo de vida do aplicativo para garantir que ele esteja ciente das dependências entre suas classes. Normalmente, os CreateMauiApp aplicativos devem executar isso no método na MauiProgram classe. A MauiProgram classe chama o CreateMauiApp método para criar um MauiAppBuilder objeto. O MauiAppBuilder objeto tem uma Services propriedade do tipo IServiceCollection, que fornece um local para registrar seus tipos, como modos de exibição, modelos de exibição e serviços para injeção de dependência:

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

Os tipos registrados com a Services propriedade são fornecidos ao contêiner de injeção de dependência quando MauiAppBuilder.Build() são chamados.

Ao registrar dependências, você precisa registrar todas as dependências, incluindo todos os tipos que exigem as dependências. Portanto, se você tiver um modelo de exibição que usa uma dependência como um parâmetro de construtor, será necessário registrar o modelo de exibição junto com todas as suas dependências. Da mesma forma, se você tiver um modo de exibição que usa uma dependência view-model como um parâmetro do construtor, será necessário registrar o modo de exibição e o view-model junto com todas as suas dependências.

Dica

Um contêiner de injeção de dependência é ideal para criar instâncias de modelo de exibição. Se um modelo de exibição tiver dependências, ele gerenciará a criação e a injeção de todos os serviços necessários. Apenas certifique-se de registrar seus modelos de exibição e quaisquer dependências que eles possam ter no CreateMauiApp método na MauiProgram classe.

Vida útil da dependência

Dependendo das necessidades do seu aplicativo, talvez seja necessário registrar dependências com tempos de vida diferentes. A tabela a seguir lista os principais métodos que você pode usar para registrar dependências e seus tempos de vida de registro:

Método Descrição
AddSingleton<T> Cria uma única instância do objeto que permanecerá durante a vida útil do aplicativo.
AddTransient<T> Cria uma nova instância do objeto quando solicitado durante a resolução. Objetos transitórios não têm um tempo de vida predefinido, mas normalmente seguirão o tempo de vida do host.
AddScoped<T> Cria uma instância do objeto que compartilha o tempo de vida de seu host. Quando o host sai do escopo, sua dependência também sai. Portanto, resolver a mesma dependência várias vezes dentro do mesmo escopo produz a mesma instância, enquanto resolver a mesma dependência em escopos diferentes produzirá instâncias diferentes.

Observação

Se um objeto não herdar de uma interface, como um modo de exibição ou modelo de exibição, somente seu tipo concreto precisará ser fornecido ao AddSingleton<T>método , AddTransient<T>ou AddScoped<T> .

A MainPageViewModel classe é usada perto da raiz do aplicativo e deve estar sempre disponível, portanto, registrá-la é AddSingleton<T> benéfico. Outros modelos de exibição podem ser navegados ou usados posteriormente em um aplicativo. Se você tem um tipo que nem sempre pode ser usado, ou se é memória ou computacionalmente intensivo ou requer dados just-in-time, ele pode ser um candidato melhor para AddTransient<T> o registro.

Outra maneira comum de registrar dependências é usando o AddSingleton<TService, TImplementation>, AddTransient<TService, TImplementation>ou AddScoped<TService, TImplementation> métodos. Esses métodos adotam dois tipos - a definição de interface e a implementação concreta. Esse tipo de registro é melhor para casos em que você está implementando serviços com base em interfaces.

Depois que todos os tipos tiverem sido registrados, MauiAppBuilder.Build() devem ser chamados para criar o objeto e preencher o MauiApp contêiner de injeção de dependência com todos os tipos registrados.

Importante

Uma vez MauiAppBuilder.Build() chamados, os tipos registrados com o contêiner de injeção de dependência serão imutáveis e não poderão mais ser atualizados ou modificados.

Registrar dependências com um método de extensão

O MauiApp.CreateBuilder método cria um MauiAppBuilder objeto que pode ser usado para registrar dependências. Se seu aplicativo precisar registrar muitas dependências, você poderá criar métodos de extensão para ajudar a fornecer um fluxo de trabalho de registro organizado e de fácil manutenção:

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

Neste exemplo, os três métodos de extensão de registro usam a instância para acessar a MauiAppBuilderServices propriedade para registrar dependências.

Resolução

Depois que um tipo é registrado, ele pode ser resolvido ou injetado como uma dependência. Quando um tipo está sendo resolvido e o contêiner precisa criar uma nova instância, ele injeta todas as dependências na instância.

Geralmente, quando um tipo é resolvido, um dos três cenários acontece:

  1. Se o tipo não tiver sido registrado, o contêiner gerará uma exceção.
  2. Se o tipo tiver sido registrado como singleton, o contêiner retornará a instância singleton. Se essa for a primeira vez que o tipo for chamado, o contêiner o criará se necessário e manterá uma referência a ele.
  3. Se o tipo tiver sido registrado como transitório, o contêiner retornará uma nova instância e não manterá uma referência a ela.

O .NET MAUI oferece suporte à resolução automática e explícita de dependências. A resolução automática de dependência usa injeção de construtor sem solicitar explicitamente a dependência do contêiner. A resolução explícita de dependência ocorre sob demanda, solicitando explicitamente uma dependência do contêiner.

Resolução automática de dependências

A resolução automática de dependência ocorre em aplicativos que usam o Shell MAUI do .NET, desde que você tenha registrado o tipo da dependência e o tipo que usa a dependência com o contêiner de injeção de dependência.

Durante a navegação baseada em Shell, o .NET MAUI procurará registros de página e, se algum for encontrado, criará essa página e injetará quaisquer dependências em seu construtor:

public MainPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

Neste exemplo, o MainPage construtor recebe uma MainPageViewModel instância que é injetada. Por sua vez, a MainPageViewModel instância tem ILoggingService e ISettingsService instâncias injetadas:

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

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

Além disso, em um aplicativo baseado em Shell, o .NET MAUI injetará dependências em páginas de detalhes registradas com o Routing.RegisterRoute método.

Resolução explícita de dependências

Um aplicativo baseado em Shell não pode usar injeção de construtor quando um tipo expõe apenas um construtor sem parâmetro. Como alternativa, se seu aplicativo não usar o Shell, você precisará usar a resolução de dependência explícita.

O contêiner de injeção de dependência pode ser acessado explicitamente de uma Element propriedade through Handler.MauiContext.Service , que é do tipo IServiceProvider:

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

        HandlerChanged += OnHandlerChanged;
    }

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

Essa abordagem pode ser útil se você precisar resolver uma dependência de um , ou de fora do construtor de um ElementElement. Neste exemplo, acessar o contêiner de injeção de dependência no HandlerChanged manipulador de eventos garante que um manipulador tenha sido definido para a página e, portanto, que a Handler propriedade não será null.

Aviso

A Handler propriedade do seu Element pode ser null, então esteja ciente de que você pode precisar prestar contas para esta situação. Para obter mais informações, consulte Ciclo de vida do manipulador.

Em um modelo de exibição, o contêiner de injeção de dependência pode ser acessado explicitamente por meio da Handler.MauiContext.Service propriedade de 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>();
    }
}

Uma desvantagem dessa abordagem é que o modelo de exibição agora tem uma dependência do Application tipo. No entanto, essa desvantagem pode ser eliminada passando um IServiceProvider argumento para o construtor view-model. O IServiceProvider é resolvido por meio da resolução automática de dependências sem precisar registrá-lo no contêiner de injeção de dependência. Com essa abordagem, um tipo e sua IServiceProvider dependência podem ser resolvidos automaticamente, desde que o tipo seja registrado no contêiner de injeção de dependência. O IServiceProvider pode então ser usado para resolução de dependência explícita:

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

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

Além disso, uma IServiceProvider instância pode ser acessada por meio das seguintes propriedades nativas:

  • Andróide- MauiApplication.Current.Services
  • iOS e Mac Catalyst - MauiUIApplicationDelegate.Current.Services
  • Windows - MauiWinUIApplication.Current.Services

Limitações com recursos XAML

Um cenário comum é registrar uma página com o contêiner de injeção de dependência e usar a App resolução automática de dependência para injetá-la no construtor e defini-la como o valor da MainPage propriedade:

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

No entanto, nesse cenário, se MyFirstAppPage as tentativas de acessar um que foi declarado App em XAML no dicionário de recursos, um StaticResourceXamlParseException será lançado com uma mensagem semelhante a Position {row}:{column}. StaticResource not found for key {key}. Isso ocorre porque a página resolvida por meio da injeção do construtor foi criada antes que os recursos XAML no nível do aplicativo tenham sido inicializados.

Uma solução alternativa para esse problema é injetar um IServiceProvider em sua App classe e, em seguida, usá-lo para resolver a página dentro da App classe:

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

Essa abordagem força a árvore de objetos XAML a ser criada e inicializada antes que a página seja resolvida.