Partilhar via


Compreender noções básicas de injeção de dependência no .NET

Neste artigo, você cria um aplicativo de console .NET que cria manualmente um ServiceCollection e correspondente ServiceProvider. Você aprende como registrar serviços e resolvê-los usando a injeção de dependência (DI). Este artigo usa o pacote NuGet Microsoft.Extensions.DependencyInjection para demonstrar os conceitos básicos de DI no .NET.

Nota

Este artigo não aproveita os recursos de Host Genérico. Para obter um guia mais abrangente, consulte Usar injeção de dependência no .NET.

Começar agora

Para começar, crie um novo aplicativo de console .NET chamado DI.Basics. Algumas das abordagens mais comuns para criar um projeto de console são referenciadas na lista a seguir:

Você precisa adicionar a referência de pacote para o Microsoft.Extensions.DependencyInjection no arquivo de projeto. Independentemente da abordagem, verifique se o projeto é semelhante ao seguinte XML do arquivo DI.Basics.csproj :

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  </ItemGroup>

</Project>

Noções básicas de injeção de dependência

A injeção de dependência é um padrão de design que permite remover dependências codificadas e tornar seu aplicativo mais fácil de manter e testar. DI é uma técnica para alcançar Inversão de Controle (IoC) entre classes e suas dependências.

As abstrações para DI no .NET são definidas no pacote NuGet Microsoft.Extensions.DependencyInjection.Abstractions :

  • IServiceCollection: Define um contrato para uma coleção de descritores de serviços.
  • IServiceProvider: Define um mecanismo para recuperar um objeto de serviço.
  • ServiceDescriptor: Descreve um serviço com seu tipo de serviço, implementação e tempo de vida.

No .NET, DI é gerenciado adicionando serviços e configurando-os em um IServiceCollectionarquivo . Depois que os serviços são registrados, uma IServiceProvider instância é criada chamando o BuildServiceProvider método. O IServiceProvider atua como um contêiner de todos os serviços registrados, e é usado para resolver serviços.

Criar serviços de exemplo

Nem todos os serviços são criados da mesma forma. Alguns serviços exigem uma nova instância cada vez que o contêiner de serviço os obtém (transitório), enquanto outros devem ser compartilhados entre solicitações (com escopo) ou durante todo o tempo de vida do aplicativo (singleton). Para obter mais informações sobre o tempo de vida do serviço, consulte Tempo de vida do serviço.

Da mesma forma, alguns serviços expõem apenas um tipo concreto, enquanto outros são expressos como um contrato entre uma interface e um tipo de implementação. Você cria várias variações de serviços para ajudar a demonstrar esses conceitos.

Crie um novo arquivo C# chamado IConsole.cs e adicione o seguinte código:

public interface IConsole
{
    void WriteLine(string message);
}

Este arquivo define uma IConsole interface que expõe um único método, WriteLine. Em seguida, crie um novo arquivo C# chamado DefaultConsole.cs e adicione o seguinte código:

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

O código anterior representa a implementação padrão da IConsole interface. O WriteLine método grava condicionalmente no console com base na IsEnabled propriedade.

Gorjeta

A nomeação de uma implementação é uma escolha com a qual sua equipe de desenvolvimento deve concordar. O Default prefixo é uma convenção comum para indicar uma implementação padrão de uma interface, mas não é necessário.

Em seguida, crie um arquivo IGreetingService.cs e adicione o seguinte código C#:

public interface IGreetingService
{
    string Greet(string name);
}

Em seguida, adicione um novo arquivo C# chamado DefaultGreetingService.cs_ e adicione o seguinte código:

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

O código anterior representa a implementação padrão da IGreetingService interface. A implementação do serviço requer um IConsole parâmetro como um construtor primário. O Greet método:

  • Cria um greeting dado o name.
  • Chama o WriteLine método na IConsole instância.
  • Retorna o greeting para o chamador.

O último serviço a ser criado é o arquivo FarewellService.cs , adicione o seguinte código C# antes de continuar:

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

O FarewellService representa um tipo concreto, não uma interface. Deve ser declarado public de modo a torná-lo acessível aos consumidores. Ao contrário de outros tipos de implementação de serviço que foram declarados como internal e sealed, este código demonstra que nem todos os serviços precisam ser interfaces. Ele também mostra que as implementações de serviço podem ser sealed para evitar herança e internal restringir o acesso ao assembly.

Atualizar a Program classe

Abra o arquivo Program.cs e substitua o código existente pelo seguinte código C#:

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

O código atualizado anterior demonstra o como:

  • Crie uma nova ServiceCollection instância.
  • Registre e configure serviços no ServiceCollection:
    • O IConsole uso da sobrecarga de fábrica de implementação, retornar um DefaultConsole tipo com o IsEnabled conjunto como 'true.
    • O IGreetingService é adicionado com um tipo de DefaultGreetingService implementação correspondente.
    • O FarewellService é adicionado como um tipo de concreto.
  • Construa o ServiceProvider a partir do ServiceCollection.
  • Resolva os IGreetingService e FarewellService serviços.
  • Use os serviços resolvidos para cumprimentar e dizer adeus a uma pessoa chamada David.

Se você atualizar a IsEnabled propriedade do DefaultConsole to false, os métodos e SayGoodbye omitirão Greet a gravação nas mensagens resultantes no console. Uma mudança como essa ajuda a demonstrar que o serviço é injetado IConsole nos serviços e FarewellService como uma dependência que influencia o comportamento dos IGreetingService aplicativos.

Todos esses serviços são registrados como singletons, embora para esta amostra, funcione de forma idêntica se eles foram registrados como serviços transitórios ou com escopo.

Importante

Neste exemplo inventado, os tempos de vida do serviço não importam, mas em um aplicativo do mundo real, você deve considerar cuidadosamente o tempo de vida de cada serviço.

Execute a aplicação de exemplo

Para executar o aplicativo de exemplo, pressione F5 no Visual Studio, Visual Studio Code ou execute o dotnet run comando no terminal. Quando o aplicativo for concluído, você verá a seguinte saída:

Hello, David!
Goodbye, David!

Descritores de serviço

As APIs mais comumente usadas para adicionar serviços ao ServiceCollection são métodos de extensão genéricos nomeados pelo tempo de vida, como:

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

Esses métodos são métodos de conveniência que criam uma ServiceDescriptor instância e a adicionam ao ServiceCollection. O ServiceDescriptor é uma classe simples que descreve um serviço com seu tipo de serviço, tipo de implementação e tempo de vida. Ele também pode desribar fábricas de implementação e instâncias.

Para cada um dos serviços registrados no ServiceCollection, você pode chamar o Add método diretamente com uma ServiceDescriptor instância. Considere os seguintes exemplos:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

O código anterior é equivalente à forma como o serviço foi registado IConsole no ServiceCollection. O Add método é usado para adicionar uma ServiceDescriptor instância que descreve o IConsole serviço. O método ServiceDescriptor.Describe estático delega a vários ServiceDescriptor construtores. Considere o código equivalente para o IGreetingService serviço:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

O código anterior descreve o IGreetingService serviço com seu tipo de serviço, tipo de implementação e tempo de vida. Finalmente, considere o código equivalente para o FarewellService serviço:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

O código anterior descreve o tipo concreto FarewellService como os tipos de serviço e implementação. O serviço está registado como um serviço singleton.

Consulte também