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:
- Visual Studio: menu Arquivo > Novo > Projeto .
- Código do Visual Studio e a extensão do C# Dev Kit: opção de menu Gerenciador de Soluções.
- .NET CLI:
dotnet new console
comando no terminal.
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 IServiceCollection
arquivo . 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 example serviços
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 (transient), enquanto outros devem ser compartilhados entre solicitações (scoped) 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 oname
. - Chama o
WriteLine
método naIConsole
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 umDefaultConsole
tipo com oIsEnabled
conjunto como 'true. - O
IGreetingService
é adicionado com um tipo deDefaultGreetingService
implementação correspondente. - O
FarewellService
é adicionado como um tipo de concreto.
- O
- Construa o
ServiceProvider
a partir doServiceCollection
. - Resolva os
IGreetingService
eFarewellService
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 transient ou scoped serviços.
Importante
Neste inventado example, 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 singleton serviço.
Consulte também
- Injeção de dependência do .NET
- Diretrizes de injeção de dependência
- Dependency injection in ASP.NET Core (Injeção de dependências no ASP.NET Core)