Compartilhar via


Injeção de dependência

Dica

Esse conteúdo é um trecho do livro eletrônico, Padrões de Aplicativo Empresarial Usando .NETMAUI, disponível em .NET Docs ou em PDF para download gratuito que pode ser lido off-line.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

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.

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.

Há 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 vistos. Portanto, este capítulo se concentrará apenas na execução da injeção de construtor com um contêiner de injeção de dependência.

Introdução à injeção de dependência

A injeção de dependência é uma versão especializada do padrão de IoC (Inversão de Controle), em que 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. O exemplo de código a seguir mostra como a classe ProfileViewModel é estruturada ao usar a injeção de dependência:

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
}

O construtor ProfileViewModel recebe várias instâncias de objeto de interface como argumentos injetados por outra classe. A única dependência na classe ProfileViewModel é nos tipos de interface. Portanto, a classe ProfileViewModel não tem nenhum conhecimento da classe responsável por instanciar os objetos de interface. A classe responsável por instanciar os objetos de interface e inseri-los na ProfileViewModel classe é 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 nele. Se essas dependências ainda 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 MVVM, um contêiner de injeção de dependência normalmente será usado para registrar e resolver exibições, registrar e resolver modelos de exibição e para registrar serviços e injetá-los em modelos de exibição.

Há muitos contêineres de injeção de dependência disponíveis no .NET; o aplicativo multiplataforma eShopOnContainers usa Microsoft.Extensions.DependencyInjection para gerenciar a instanciação de exibições, modelos de exibição e classes de serviço no 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.

No .NET MAUI, a classe MauiProgram chamará o método CreateMauiApp para criar um objeto MauiAppBuilder. O objeto MauiAppBuilder tem uma propriedade Services do tipo IServiceCollection, que fornece um local para registrar nossos componentes, como exibições, modelos de exibição e serviços para injeção de dependência. Todos os componentes registrados com a propriedade Services serão fornecidos ao contêiner de injeção de dependência quando o método MauiAppBuilder.Build for chamado.

Em runtime, o contêiner deve saber qual implementação dos serviços está sendo solicitada para instanciá-los para os objetos solicitados. No aplicativo multiplataforma eShopOnContainers, as interfaces IAppEnvironmentService, IDialogService, INavigationService e ISettingsService precisam ser resolvidas antes de criar uma instância de um objeto ProfileViewModel. Isso envolve o contêiner executando as seguintes ações:

  • Decidindo como instanciar um objeto que implementa a interface. Isso é conhecido como registro.
  • Instanciando o objeto que implementa a interface necessária e o objeto ProfileViewModel. Isso é conhecido como resolução.

Por fim, o aplicativo terminará de usar o objeto ProfileViewModel e ele ficará disponível para coleta de lixo. Neste ponto, o coletor de lixo deverá descartar quaisquer implementações de interface de curta duração se outras classes não compartilharem a mesma instância.

Registro

Antes que as dependências possam ser injetadas em um objeto, os tipos das dependências devem primeiro ser registrados com o contêiner. Registrar um tipo envolve passar ao contêiner uma interface e um tipo concreto que implementa a interface.

Há duas maneiras de registrar tipos e objetos no contêiner por meio do código:

  • 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.

Observação

Os contêineres de injeção de dependência nem sempre são adequados. A injeção de dependência introduz complexidade e requisitos adicionais que podem não ser apropriados ou úteis para aplicativos pequenos. 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 sejam integrais ao tipo e nunca forem alteradas, talvez não faça sentido colocá-la no contêiner.

O registro de tipos que exigem injeção de dependência deve ser executado em um único método em um aplicativo. Esse método deve ser invocado no início do ciclo de vida do aplicativo para garantir que ele esteja ciente das dependências entre suas classes. O aplicativo multiplataforma eShopOnContainers executa esse método MauiProgram.CreateMauiApp. O exemplo de código a seguir mostra como o aplicativo multiplataforma eShopOnContainers declara o CreateMauiApp na classe MauiProgram:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

O método MauiApp.CreateBuilder cria um objeto MauiAppBuilder que podemos usar para registrar nossas dependências. Muitas dependências no aplicativo multiplataforma eShopOnContainers precisam ser registradas, portanto, os métodos de extensão RegisterAppServices, RegisterViewModelse RegisterViews foram criados para ajudar a fornecer um fluxo de trabalho de registro organizado e de fácil manutenção. O código a seguir mostra o método RegisterViewModels:

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

Esse método recebe uma instância de MauiAppBuilder e podemos usar a propriedade Services para registrar nossos modelos de exibição. Dependendo das necessidades do aplicativo, talvez seja necessário adicionar serviços com tempos de vida diferentes. A tabela a seguir fornece informações sobre quando você pode querer escolher esses diferentes tempos de vida de registro:

Método Descrição
AddSingleton<T> Criará uma única instância do objeto que permanecerá durante o tempo de vida do aplicativo.
AddTransient<T> Criará 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.

Observação

Os modelos de exibição não herdam de uma interface, portanto, só precisam do tipo concreto fornecido aos métodos AddSingleton<T> e AddTransient<T>.

O CatalogViewModel é usado próximo à raiz do aplicativo e deve estar sempre disponível, portanto, registrá-lo com AddSingleton<T> é benéfico. Outros modelos de exibição, como CheckoutViewModel e OrderDetailViewModel navegam até ele conforme a situação ou são usados posteriormente no aplicativo. Suponha que você saiba que tem um componente que nem sempre pode ser usado. Nesse caso, se for memória, uso intensivo computacional ou exigir dados just-in-time, ele poderá ser um candidato melhor para o registro AddTransient<T>.

Outra maneira comum de adicionar serviços é usando os métodos AddSingleton<TService, TImplementation> e AddTransient<TService, TImplementation>. Esses métodos têm dois tipos de entrada: 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. No exemplo de código abaixo, registramos nossa interface ISettingsService usando a implementação SettingsService:

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Depois que todos os serviços forem registrados, o método MauiAppBuilder.Build deverá ser chamado para criar nosso MauiApp e preencher nosso contêiner de injeção de dependência com todos os serviços registrados.

Importante

Depois que o método Build for chamado, os serviços 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.

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, uma das três coisas 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 várias maneiras de resolver componentes registrados com base em suas necessidades. A maneira mais direta de obter acesso ao contêiner de injeção de dependência é de um Element usando o Handler.MauiContext.Services. Um exemplo disso é mostrado abaixo:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Isso poderá ser útil se você precisar resolver um serviço de dentro de um Element ou de fora do construtor de seu Element.

Cuidado

Há a possibilidade de que a propriedade Handler de seu Element possa ser nula, portanto, lembre-se de que talvez seja necessário lidar com essas situações. Para obter mais informações, confira Ciclo de vida do manipulador no Centro de Documentação da Microsoft.

Se estiver usando o controle Shell para .NET MAUI, ele chamará implicitamente o contêiner de injeção de dependência para criar nossos objetos durante a navegação. Ao configurar nosso controle Shell, o método Routing.RegisterRoute vinculará um caminho de rota a um View, conforme mostrado no exemplo abaixo:

Routing.RegisterRoute("Filter", typeof(FiltersView));

Durante a navegação do Shell, ele procurará registros do FiltersView e, se algum for encontrado, ele criará essa exibição e injetará dependências no construtor. Conforme mostrado no exemplo de código abaixo, o CatalogViewModel será injetado no FiltersView:

namespace eShopOnContainers.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Dica

O contêiner de injeção de dependência é ótimo para criar instâncias de modelo de exibição. Se um modelo de exibição tiver dependências, ele manipulará 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 com o método CreateMauiApp na classe MauiProgram.

Resumo

A injeção de dependência permite o desacoplamento de tipos concretos do código que depende desses tipos. Normalmente, 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.

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.