Injeção de dependência do .NET
O .NET suporta o padrão de design de software de injeção de dependência (DI), que é uma técnica para alcançar Inversão de Controle (IoC) entre classes e suas dependências. A injeção de dependência no .NET é uma parte interna da estrutura, juntamente com a configuração, o registro em log e o padrão de opções.
Uma dependência é um objeto do qual outro objeto depende. Examine a seguinte MessageWriter
classe com um Write
método do qual outras classes dependem:
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Uma classe pode criar uma instância da MessageWriter
classe para fazer uso de seu Write
método. No exemplo a seguir, a MessageWriter
classe é uma dependência da Worker
classe:
public class Worker : BackgroundService
{
private readonly MessageWriter _messageWriter = new();
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
A classe cria e depende diretamente da MessageWriter
classe. Dependências codificadas, como no exemplo anterior, são problemáticas e devem ser evitadas pelos seguintes motivos:
- Para substituir
MessageWriter
por uma implementação diferente, aWorker
classe deve ser modificada. - Se
MessageWriter
tiver dependências, elas também devem ser configuradasWorker
pela classe. Em um projeto grande com várias classes, dependendoMessageWriter
do , o código de configuração fica espalhado pelo aplicativo. - Esta implementação é difícil de testar por unidade. O aplicativo deve usar uma classe simulada ou stub
MessageWriter
, o que não é possível com essa abordagem.
A injeção de dependência resolve esses problemas através de:
- O uso de uma interface ou classe base para abstrair a implementação de dependência.
- Registro da dependência em um contêiner de serviço. O .NET fornece um contêiner de serviço interno, IServiceProvider. Normalmente, os serviços são registados no arranque da aplicação e anexados a um IServiceCollectionficheiro . Depois que todos os serviços forem adicionados, você usará BuildServiceProvider para criar o contêiner de serviço.
- Injeção do serviço no construtor da classe onde ele é usado. A estrutura assume a responsabilidade de criar uma instância da dependência e eliminá-la quando ela não for mais necessária.
Como exemplo, a IMessageWriter
interface define o Write
método:
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
Esta interface é implementada por um tipo concreto, MessageWriter
:
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
O código de exemplo registra o IMessageWriter
serviço com o tipo MessageWriter
concreto. O AddSingleton método registra o serviço com um tempo de vida único, o tempo de vida do aplicativo. Os tempos de vida do serviço são descritos mais adiante neste artigo.
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
No código anterior, o aplicativo de exemplo:
Cria uma instância do construtor de aplicativos host.
Configura os serviços registrando:
- O
Worker
como um serviço hospedado. Para obter mais informações, consulte Serviços de trabalho no .NET. - A
IMessageWriter
interface como um serviço singleton com uma implementação correspondente daMessageWriter
classe.
- O
Cria o host e o executa.
O host contém o provedor de serviços de injeção de dependência. Ele também contém todos os outros serviços relevantes necessários para instanciar automaticamente o Worker
e fornecer a implementação correspondente IMessageWriter
como um argumento.
namespace DependencyInjection.Example;
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
Usando o padrão DI, o serviço do trabalhador:
- Não usa o tipo
MessageWriter
concreto, apenas aIMessageWriter
interface que o implementa. Isso facilita a alteração da implementação que o serviço de trabalhador usa sem modificar o serviço de trabalho. - Não cria uma instância do
MessageWriter
. A instância é criada pelo contêiner DI.
A implementação da interface pode ser melhorada usando a API de IMessageWriter
log integrada:
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
O método atualizado AddSingleton
registra a nova IMessageWriter
implementação:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
O HostApplicationBuilder tipo (builder
) faz parte do Microsoft.Extensions.Hosting
pacote NuGet.
LoggingMessageWriter
depende de ILogger<TCategoryName>, que ele solicita no construtor. ILogger<TCategoryName>
é um serviço prestado pelo quadro.
Não é incomum usar a injeção de dependência de forma encadeada. Cada dependência solicitada, por sua vez, solicita suas próprias dependências. O contêiner resolve as dependências no gráfico e retorna o serviço totalmente resolvido. O conjunto coletivo de dependências que devem ser resolvidas é normalmente chamado de árvore de dependência, gráfico de dependência ou gráfico de objeto.
O recipiente ILogger<TCategoryName>
resolve aproveitando os tipos abertos (genéricos), eliminando a necessidade de registrar todos os tipos construídos (genéricos).
Com a terminologia de injeção de dependência, um serviço:
- Normalmente, é um objeto que fornece um serviço para outros objetos, como o
IMessageWriter
serviço. - Não está relacionado a um serviço Web, embora o serviço possa usar um serviço Web.
A estrutura fornece um sistema de registro robusto. As IMessageWriter
implementações mostradas nos exemplos anteriores foram escritas para demonstrar DI básica, não para implementar o registro em log. A maioria dos aplicativos não precisa escrever loggers. O código a seguir demonstra o uso do log padrão, que requer apenas o Worker
registro como um serviço AddHostedServicehospedado :
public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1_000, stoppingToken);
}
}
}
Usando o código anterior, não há necessidade de atualizar Program.cs, porque o log é fornecido pela estrutura.
Várias regras de descoberta de construtores
Quando um tipo define mais de um construtor, o provedor de serviços tem lógica para determinar qual construtor usar. O construtor com mais parâmetros onde os tipos são DI-resolvable é selecionado. Considere o seguinte serviço de exemplo em C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
No código anterior, suponha que o log foi adicionado e pode ser resolvido pelo provedor de serviços, mas os FooService
tipos e BarService
não são. O construtor com o ILogger<ExampleService>
parâmetro é usado para resolver a ExampleService
instância. Mesmo que haja um construtor que define mais parâmetros, os FooService
e BarService
tipos não são DI-resolvíveis.
Se houver ambiguidade ao descobrir construtores, uma exceção é lançada. Considere o seguinte serviço de exemplo em C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Aviso
O ExampleService
código com parâmetros de tipo DI-resolvíveis ambíguos lançaria uma exceção. Não faça isso — destina-se a mostrar o que se entende por "tipos ambíguos DI-resolvíveis".
No exemplo anterior, há três construtores. O primeiro construtor é sem parâmetros e não requer serviços do provedor de serviços. Suponha que o registro em log e as opções foram adicionados ao contêiner DI e são serviços DI-resolvíveis. Quando o contêiner DI tenta resolver o ExampleService
tipo, ele lançará uma exceção, pois os dois construtores são ambíguos.
Você pode evitar ambiguidade definindo um construtor que aceite ambos os tipos DI-resolvable:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Registrar grupos de serviços com métodos de extensão
O Microsoft Extensions usa uma convenção para registrar um grupo de serviços relacionados. A convenção é usar um único Add{GROUP_NAME}
método de extensão para registrar todos os serviços exigidos por um recurso de estrutura. Por exemplo, o AddOptions método de extensão registra todos os serviços necessários para usar opções.
Serviços prestados no quadro
Ao usar qualquer um dos padrões de host ou construtor de aplicativos disponíveis, os padrões são aplicados e os serviços são registrados pela estrutura. Considere alguns dos padrões de host e construtor de aplicativos mais populares:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
Depois de criar um construtor a partir de qualquer uma dessas APIs, o IServiceCollection
tem serviços definidos pela estrutura, dependendo de como o host foi configurado. Para aplicativos baseados nos modelos .NET, a estrutura pode registrar centenas de serviços.
A tabela a seguir lista uma pequena amostra desses serviços registrados na estrutura:
Tipo de Serviço | Vitalício |
---|---|
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Singleton |
IHostApplicationLifetime | Singleton |
Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Transitório |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
Vida útil do serviço
Os serviços podem ser registrados com um dos seguintes tempos de vida:
As seções a seguir descrevem cada um dos tempos de vida anteriores. Escolha um tempo de vida adequado para cada serviço registado.
Transitório
Os serviços de tempo de vida transitório são criados sempre que são solicitados a partir do contêiner de serviço. Para registrar um serviço como transitório, ligue AddTransientpara .
Em aplicativos que processam solicitações, os serviços transitórios são descartados no final da solicitação. Esse tempo de vida incorre em alocações por solicitação, pois os serviços são resolvidos e construídos sempre. Para obter mais informações, consulte Diretrizes de injeção de dependência: orientação IDisposable para instâncias transitórias e compartilhadas.
Âmbito de aplicação
Para aplicativos Web, um tempo de vida definido indica que os serviços são criados uma vez por solicitação do cliente (conexão). Registre serviços com escopo com AddScopedo .
Em aplicativos que processam solicitações, os serviços com escopo são descartados no final da solicitação.
Ao usar o Entity Framework Core, o AddDbContext método de DbContext
extensão registra tipos com um tempo de vida com escopo por padrão.
Nota
Não resolva um serviço com escopo a partir de um singleton e tenha cuidado para não fazê-lo indiretamente, por exemplo, através de um serviço transitório. Isso pode fazer com que o serviço tenha um estado incorreto ao processar solicitações subsequentes. Não há problema em:
- Resolva um serviço singleton a partir de um serviço com escopo ou transitório.
- Resolva um serviço com escopo de outro serviço com escopo ou transitório.
Por padrão, no ambiente de desenvolvimento, a resolução de um serviço de outro serviço com uma vida útil mais longa gera uma exceção. Para obter mais informações, consulte Validação de escopo.
Singleton
Os serviços vitalícios Singleton são criados:
- A primeira vez que são solicitados.
- Pelo desenvolvedor, ao fornecer uma instância de implementação diretamente para o contêiner. Esta abordagem raramente é necessária.
Cada solicitação subsequente da implementação do serviço do contêiner de injeção de dependência usa a mesma instância. Se o aplicativo exigir um comportamento singleton, permita que o contêiner de serviço gerencie o tempo de vida do serviço. Não implemente o padrão de design singleton e forneça código para descartar o singleton. Os serviços nunca devem ser descartados por código que resolveu o serviço do contêiner. Se um tipo ou fábrica estiver registado como singleton, o contentor elimina o singleton automaticamente.
Registre serviços singleton com AddSingleton. Os serviços singleton devem ser thread safe e são frequentemente usados em serviços sem monitoração de estado.
Em aplicativos que processam solicitações, os serviços singleton são descartados quando o é descartado no desligamento do ServiceProvider aplicativo. Como a memória não é liberada até que o aplicativo seja desligado, considere o uso de memória com um serviço singleton.
Métodos de registo de serviços
A estrutura fornece métodos de extensão de registro de serviço que são úteis em cenários específicos:
Método | Automático objeto eliminação |
Várias Implementações |
Passe args |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() Exemplo: services.AddSingleton<IMyDep, MyDep>(); |
Sim | Sim | No |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Exemplos: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
Sim | Sim | Sim |
Add{LIFETIME}<{IMPLEMENTATION}>() Exemplo: services.AddSingleton<MyDep>(); |
Sim | No | Não |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) Exemplos: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); |
Não | Sim | Sim |
AddSingleton(new {IMPLEMENTATION}) Exemplos: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); |
No | No | Sim |
Para obter mais informações sobre a eliminação por tipo, consulte a seção Descarte de serviços .
Registrar um serviço com apenas um tipo de implementação é equivalente a registrar esse serviço com a mesma implementação e tipo de serviço. Por exemplo, considere o seguinte código:
services.AddSingleton<ExampleService>();
Isso equivale a registrar o serviço com o serviço e a implementação dos mesmos tipos:
services.AddSingleton<ExampleService, ExampleService>();
Essa equivalência é o motivo pelo qual várias implementações de um serviço não podem ser registradas usando os métodos que não usam um tipo de serviço explícito. Esses métodos podem registrar várias instâncias de um serviço, mas todos eles terão o mesmo tipo de implementação .
Qualquer um dos métodos de registro de serviço acima pode ser usado para registrar várias instâncias de serviço do mesmo tipo de serviço. No exemplo a seguir, AddSingleton
é chamado duas vezes com IMessageWriter
como o tipo de serviço. A segunda chamada para AddSingleton
substituir a anterior quando resolvida como IMessageWriter
e adiciona à anterior quando vários serviços são resolvidos via IEnumerable<IMessageWriter>
. Os serviços aparecem na ordem em que foram registrados quando resolvidos via IEnumerable<{SERVICE}>
.
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();
using IHost host = builder.Build();
_ = host.Services.GetService<ExampleService>();
await host.RunAsync();
O código-fonte de exemplo anterior registra duas implementações do IMessageWriter
.
using System.Diagnostics;
namespace ConsoleDI.IEnumerableExample;
public sealed class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is LoggingMessageWriter);
var dependencyArray = messageWriters.ToArray();
Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
}
}
O ExampleService
define dois parâmetros do construtor: um único IMessageWriter
e um IEnumerable<IMessageWriter>
. O único IMessageWriter
é a última implementação a ter sido registrada, enquanto o IEnumerable<IMessageWriter>
representa todas as implementações registradas.
A estrutura também fornece TryAdd{LIFETIME}
métodos de extensão, que registram o serviço somente se ainda não houver uma implementação registrada.
No exemplo a seguir, a chamada para AddSingleton
registros ConsoleMessageWriter
como uma implementação para IMessageWriter
. A chamada para TryAddSingleton
não tem efeito porque IMessageWriter
já tem uma implementação registrada:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
O TryAddSingleton
não tem efeito, pois já foi adicionado e a "tentativa" falhará. O ExampleService
afirmaria o seguinte:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
Para obter mais informações, consulte:
Os métodos TryAddEnumerable(ServiceDescriptor) registram o serviço somente se ainda não houver uma implementação do mesmo tipo. Vários serviços são resolvidos via IEnumerable<{SERVICE}>
. Ao registrar serviços, adicione uma instância se um dos mesmos tipos ainda não tiver sido adicionado. Os autores da biblioteca usam TryAddEnumerable
para evitar registrar várias cópias de uma implementação no contêiner.
No exemplo a seguir, a primeira chamada para TryAddEnumerable
registros MessageWriter
como uma implementação para IMessageWriter1
. O segundo convite regista-se MessageWriter
para IMessageWriter2
. A terceira chamada não tem efeito porque IMessageWriter1
já tem uma implementação registrada de MessageWriter
:
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }
public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
O registro do serviço geralmente é independente da ordem, exceto quando se registram várias implementações do mesmo tipo.
IServiceCollection
é uma coleção de ServiceDescriptor objetos. O exemplo a seguir mostra como registrar um serviço criando e adicionando um ServiceDescriptor
:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
Os métodos internos Add{LIFETIME}
usam a mesma abordagem. Por exemplo, consulte o código-fonte AddScoped.
Comportamento de injeção do construtor
Os serviços podem ser resolvidos usando:
- IServiceProvider
- ActivatorUtilities:
- Cria objetos que não estão registrados no contêiner.
- Usado com alguns recursos de estrutura.
Os construtores podem aceitar argumentos que não são fornecidos pela injeção de dependência, mas os argumentos devem atribuir valores padrão.
Quando os serviços são resolvidos por IServiceProvider
ou ActivatorUtilities
, a injeção do construtor requer um construtor público .
Quando os serviços são resolvidos pelo ActivatorUtilities
, a injeção do construtor requer que exista apenas um construtor aplicável. Sobrecargas de construtor são suportadas, mas apenas uma sobrecarga pode existir cujos argumentos podem ser todos atendidos por injeção de dependência.
Validação do âmbito
Quando o aplicativo é executado no Development
ambiente e chama CreateApplicationBuilder para criar o host, o provedor de serviços padrão executa verificações para verificar se:
- Os serviços com escopo não são resolvidos a partir do provedor de serviços raiz.
- Os serviços com escopo não são injetados em singletons.
O provedor de serviços raiz é criado quando BuildServiceProvider é chamado. O tempo de vida do provedor de serviços raiz corresponde ao tempo de vida do aplicativo quando o provedor começa com o aplicativo e é descartado quando o aplicativo é desligado.
Os serviços com escopo são descartados pelo contêiner que os criou. Se um serviço com escopo for criado no contêiner raiz, o tempo de vida do serviço será efetivamente promovido para singleton porque ele só será descartado pelo contêiner raiz quando o aplicativo for desligado. A validação de escopos de serviço deteta essas situações quando BuildServiceProvider
é chamada.
Cenários de escopo
O IServiceScopeFactory é sempre registrado como um singleton, mas o IServiceProvider pode variar com base na vida útil da classe que contém. Por exemplo, se você resolver serviços de um escopo e qualquer um desses serviços usar um IServiceProvider, será uma instância com escopo.
Para obter serviços de escopo dentro de implementações do , como o BackgroundService, não injete as dependências de serviço por meio da injeção do IHostedServiceconstrutor. Em vez disso, injete IServiceScopeFactory, crie um escopo e resolva dependências do escopo para usar o tempo de vida de serviço apropriado.
namespace WorkerScope.Example;
public sealed class Worker(
ILogger<Worker> logger,
IServiceScopeFactory serviceScopeFactory)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
try
{
logger.LogInformation(
"Starting scoped work, provider hash: {hash}.",
scope.ServiceProvider.GetHashCode());
var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
var next = await store.GetNextAsync();
logger.LogInformation("{next}", next);
var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
await processor.ProcessAsync(next);
logger.LogInformation("Processing {name}.", next.Name);
var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
await relay.RelayAsync(next);
logger.LogInformation("Processed results have been relayed.");
var marked = await store.MarkAsync(next);
logger.LogInformation("Marked as processed: {next}", marked);
}
finally
{
logger.LogInformation(
"Finished scoped work, provider hash: {hash}.{nl}",
scope.ServiceProvider.GetHashCode(), Environment.NewLine);
}
}
}
}
}
No código anterior, enquanto o aplicativo está em execução, o serviço em segundo plano:
- Depende do IServiceScopeFactory.
- Cria um IServiceScope para resolver serviços adicionais.
- Resolve serviços com escopo para consumo.
- Trabalha no processamento de objetos e, em seguida, retransmiti-los e, finalmente, marca-os como processados.
A partir do código-fonte de exemplo, você pode ver como as implementações de IHostedService podem se beneficiar de tempos de vida de serviço com escopo.
Serviços chaveados
A partir do .NET 8, há suporte para registros de serviço e pesquisas com base em uma chave, o que significa que é possível registrar vários serviços com uma chave diferente e usar essa chave para a pesquisa.
Por exemplo, considere o caso em que você tem diferentes implementações da interface IMessageWriter
: MemoryMessageWriter
e QueueMessageWriter
.
Você pode registrar esses serviços usando a sobrecarga dos métodos de registro de serviço (vistos anteriormente) que suportam uma chave como parâmetro:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
O key
não está limitado a string
, pode ser o que object
quiser, desde que o tipo implemente Equals
corretamente.
No construtor da classe que usa IMessageWriter
, você adiciona o FromKeyedServicesAttribute para especificar a chave do serviço a ser resolvido:
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
Consulte também
- Compreender noções básicas de injeção de dependência no .NET
- Usar injeção de dependência no .NET
- Diretrizes de injeção de dependência
- Dependency injection in ASP.NET Core (Injeção de dependências no ASP.NET Core)
- NDC Conference Patterns para desenvolvimento de aplicativos DI
- Princípio das dependências explícitas
- Inversão de recipientes de controle e o padrão de injeção de dependência (Martin Fowler)
- Bugs DI devem ser criados no repositório github.com/dotnet/extensions