Compartilhar via


Tutorial: Usar a injeção de dependência no .NET

Este tutorial mostra como usar a DI (injeção de dependência) no .NET. Com as Extensões da Microsoft, o DI é gerenciado adicionando serviços e configurando-os em um IServiceCollection. A IHost interface expõe a IServiceProvider instância, que atua como um contêiner de todos os serviços registrados.

Neste tutorial, você aprenderá como:

  • Criar um aplicativo de console do .NET que usa injeção de dependência
  • Criar e configurar um host genérico
  • Escrever várias interfaces e desenvolver implementações correspondentes
  • Usar o tempo de vida do serviço e o escopo para a DI

Pré-requisitos

  • SDK do .NET Core 3.1 ou posterior.
  • Familiaridade com a criação de novos aplicativos .NET e a instalação de pacotes NuGet.

Crie um novo aplicativo de console

Usando o novo comando dotnet ou um novo assistente de projeto do IDE, crie um novo aplicativo de console do .NET chamado ConsoleDI.Example. Adicione o pacote NuGet Microsoft.Extensions.Hosting ao projeto.

O novo arquivo de projeto do aplicativo de console deve ser semelhante ao seguinte:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ConsoleDI.Example</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
  </ItemGroup>

</Project>

Importante

Neste exemplo, o pacote NuGet Microsoft.Extensions.Hosting é necessário para compilar e executar o aplicativo. Alguns metapacotes podem conter o Microsoft.Extensions.Hosting pacote, caso em que uma referência de pacote explícita não é necessária.

Adicionar interfaces

Neste aplicativo de exemplo, você aprenderá como a injeção de dependência lida com o ciclo de vida do serviço. Você cria várias interfaces que representam diferentes tempos de vida do serviço. Adicione as seguintes interfaces ao diretório raiz do projeto:

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

A interface IReportServiceLifetime define o seguinte:

  • Uma Guid Id propriedade que representa o identificador exclusivo do serviço.
  • Uma propriedade ServiceLifetime que representa o tempo de vida do serviço.

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

Todas as subinterfaces de IReportServiceLifetime implementam explicitamente IReportServiceLifetime.Lifetime como padrão. Por exemplo, IExampleTransientService explicitamente implementa IReportServiceLifetime.Lifetime com o valor ServiceLifetime.Transient.

Adicionar implementações padrão

Todas as implementações de exemplo inicializam sua Id propriedade com o resultado de Guid.NewGuid(). Adicione as seguintes classes de implementação padrão para os vários serviços ao diretório raiz do projeto:

ExampleTransientService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleScopedService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleSingletonService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

Cada implementação é definida como internal sealed e implementa sua interface correspondente. Não é necessário que sejam internal ou sealed, no entanto, é comum tratar as implementações como internal para evitar o vazamento de tipos de implementação para consumidores externos. Além disso, como cada tipo não é estendido, ele é marcado como sealed. Por exemplo, ExampleSingletonService implementa IExampleSingletonService.

Adicionar um serviço que requer DI

Adicione a seguinte classe de relator de tempo de vida do serviço, que atua como um serviço para o aplicativo de console:

ServiceLifetimeReporter.cs

namespace ConsoleDI.Example;

internal sealed class ServiceLifetimeReporter(
    IExampleTransientService transientService,
    IExampleScopedService scopedService,
    IExampleSingletonService singletonService)
{
    public void ReportServiceLifetimeDetails(string lifetimeDetails)
    {
        Console.WriteLine(lifetimeDetails);

        LogService(transientService, "Always different");
        LogService(scopedService, "Changes only with lifetime");
        LogService(singletonService, "Always the same");
    }

    private static void LogService<T>(T service, string message)
        where T : IReportServiceLifetime =>
        Console.WriteLine(
            $"    {typeof(T).Name}: {service.Id} ({message})");
}

O ServiceLifetimeReporter define um construtor que requer cada uma das interfaces de serviço acima mencionadas, ou seja, IExampleTransientServicee IExampleScopedServiceIExampleSingletonService. O objeto expõe um único método que permite que o consumidor relate sobre o serviço com um determinado lifetimeDetails parâmetro. Quando invocado, o método registra o ReportServiceLifetimeDetails identificador exclusivo de cada serviço com a mensagem de tempo de vida do serviço. As mensagens de log ajudam a visualizar o tempo de vida do serviço.

Registrar serviços para integração de dados (DI)

Atualize Program.cs com o seguinte código:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

Cada services.Add{LIFETIME}<{SERVICE}> método de extensão adiciona (e potencialmente configura) serviços. Recomendamos que os aplicativos sigam esta convenção. Não coloque métodos de extensão no namespace, Microsoft.Extensions.DependencyInjection a menos que você esteja criando um pacote oficial da Microsoft. Métodos de extensão definidos no Microsoft.Extensions.DependencyInjection namespace:

  • São exibidos no IntelliSense sem a necessidade de mais using diretivas.
  • Reduza o número de diretivas de using necessárias nas classes Program ou Startup em que esses métodos de extensão são normalmente chamados.

O aplicativo:

Conclusão

Neste aplicativo de exemplo, você criou várias interfaces e implementações correspondentes. Cada um desses serviços é identificado exclusivamente e emparelhado com um ServiceLifetime. O aplicativo de exemplo demonstra o registro de implementações de serviço em uma interface e como registrar classes puras sem interfaces de suporte. Em seguida, o aplicativo de exemplo demonstra como as dependências definidas como parâmetros de construtor são resolvidas em runtime.

Quando você executa o aplicativo, ele exibe uma saída semelhante à seguinte:

// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// 
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

Na saída do aplicativo, você pode ver que:

  • Transient os serviços são sempre diferentes. Uma nova instância é criada com cada recuperação do serviço.
  • Scoped os serviços mudam apenas com um novo escopo, mas são a mesma instância dentro de um escopo.
  • Singleton os serviços são sempre os mesmos. Uma nova instância só é criada uma vez.

Consulte também