Compartilhar via


Início Rápido: Noções básicas sobre injeção de dependência no .NET

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

Observação

Este artigo não aproveita os recursos genéricos do host . Para obter um guia mais abrangente, consulte Usar a injeção de dependência no .NET.

Introdução

Para começar, crie um novo aplicativo de console do .NET chamado DI.Basics. No Visual Studio, escolha Arquivo > Novo > Projeto ou, usando a CLI do .NET, insira dotnet new console.

Em seguida, adicione uma referência de pacote ao Microsoft.Extensions.DependencyInjection no arquivo de projeto. Depois de adicionar o pacote, verifique se o projeto é semelhante ao seguinte XML do arquivo DI.Basics.csproj :

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

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

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

</Project>

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

A injeção de dependência é um padrão de design que você pode usar para remover dependências codificadas e tornar seu aplicativo mais mantenedível e testável. A DI é uma técnica para alcançar a Inversão de Controle (IoC) entre classes e suas dependências.

O pacote NuGet Microsoft.Extensions.DependencyInjection.Abstractions define as abstrações para DI no .NET:

  • IServiceCollection: define um contrato para uma coleção de descritores de serviço.
  • 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, você gerencia a DI adicionando serviços e configurando-os em um IServiceCollection. Depois de registrar serviços, chame o BuildServiceProvider método para criar uma IServiceProvider instância. Ele IServiceProvider atua como um contêiner para todos os serviços registrados e você o usa 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 sempre que o contêiner de serviço os obtém (transitório), enquanto outros devem ser compartilhados entre solicitações (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 os tempos 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);
}

Esse 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 método WriteLine grava condicionalmente no console com base na propriedade IsEnabled.

Dica

A nomenclatura de uma implementação é uma escolha em que 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ária.

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 IConsole como um parâmetro de construtor primário. O método Greet:

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

A classe DefaultGreetingService demonstra que você pode implementar seal serviços para impedir a herança e usar internal para restringir o acesso ao assembly.

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

Representa FarewellService um tipo concreto, não uma interface. Você deve declará-lo como public para torná-lo acessível aos consumidores. Ao contrário de outros tipos de implementação de serviço que você declara como internal e sealed, esse código demonstra que nem todos os serviços precisam ser interfaces.

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 anterior demonstra como:

  • Crie uma nova ServiceCollection instância.
  • Registre e configure serviços no ServiceCollection:
    • O IConsole serviço usando a sobrecarga da fábrica de implementação. Retornar um tipo DefaultConsole com a propriedade IsEnabled definida como true.
    • O IGreetingService serviço com um tipo de implementação correspondente de DefaultGreetingService.
    • O serviço FarewellService como um tipo concreto.
  • Compile a ServiceProvider partir do ServiceCollection.
  • Resolva os serviços IGreetingService e FarewellService.
  • Use os serviços resolvidos para saudar e dizer adeus a uma pessoa chamada David.

Se você atualizar a propriedade IsEnabled do DefaultConsole para false, os métodos Greet e SayGoodbye omitirão a gravação das mensagens resultantes no console. Essa alteração ajuda a demonstrar que o IConsole serviço é injetado nos serviços IGreetingService e FarewellService como uma dependência que influencia o comportamento do aplicativo.

Todos esses serviços são registrados como singletons. Para este exemplo, ele funcionará de forma idêntica se você registrá-los como serviços transientes ou de escopo definido.

Importante

Neste exemplo criado, a duração do serviço não tem relevância. Em um aplicativo do mundo real, considere cuidadosamente o tempo de vida de cada serviço.

Executar o aplicativo de exemplo

Para executar o aplicativo de exemplo, pressione F5 no Visual Studio ou no 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 usadas para adicionar serviços ao ServiceCollection são métodos de extensão genéricos nomeados conforme a duração de vida, como:

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

Esses são métodos de conveniência que criam uma instância ServiceDescriptor e a adicionam ao ServiceCollection. É 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 descrever fábricas e instâncias de implementação.

Em vez disso, para cada serviço que você registrou no ServiceCollection, você pode chamar diretamente o método Add com uma instância ServiceDescriptor. 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 a como o IConsole serviço foi registrado no ServiceCollection. O Add método adiciona 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 serviço com seu IGreetingService tipo de serviço, tipo de implementação e tempo de vida. Por fim, 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á registrado como um serviço singleton.

Consulte também