Inserción de dependencias .NET
.NET admite el patrón de diseño de software de inserción de dependencias (DI), que es una técnica para conseguir la inversión de control (IoC) entre clases y sus dependencias. La inserción de dependencias en .NET es una parte integrada del marco, junto con la configuración, el registro y el patrón de opciones.
Una dependencia es un objeto del que depende otro objeto. Examine la clase MessageWriter
siguiente con un método Write
del que dependen otras clases:
public class MessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
Una clase puede crear una instancia de la clase MessageWriter
para usar su método Write
. En el ejemplo siguiente, la clase MessageWriter
es una dependencia de la clase Worker
:
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);
}
}
}
La clase crea y depende directamente de la clase MessageWriter
. Las dependencias codificadas de forma rígida, como en el ejemplo anterior, son problemáticas y deben evitarse por las razones siguientes:
- Para reemplazar
MessageWriter
por una implementación diferente, se debe modificar la claseWorker
. - Si
MessageWriter
tiene dependencias, deben configurarse según la claseWorker
. En un proyecto grande con varias clases que dependen deMessageWriter
, el código de configuración se dispersa por la aplicación. - Esta implementación es difícil para realizar pruebas unitarias. La aplicación debe usar una clase
MessageWriter
como boceto o código auxiliar, que no es posible con este enfoque.
La inserción de dependencias aborda estos problemas mediante:
- Uso de una interfaz o clase base para abstraer la implementación de dependencias.
- Registro de la dependencia en un contenedor de servicios. .NET proporciona un contenedor de servicios integrado, IServiceProvider. Normalmente, los servicios se registran al iniciar la aplicación y se anexan a una IServiceCollection. Una vez que se agregan todos los servicios, se usa BuildServiceProvider para crear el contenedor de servicios.
- Inserción del servicio en el constructor de la clase en la que se usa. El marco de trabajo asume la responsabilidad de crear una instancia de la dependencia y de desecharla cuando ya no es necesaria.
Por ejemplo, la interfaz IMessageWriter
define el método Write
:
namespace DependencyInjection.Example;
public interface IMessageWriter
{
void Write(string message);
}
Esta interfaz se implementa mediante un tipo concreto, MessageWriter
:
namespace DependencyInjection.Example;
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
El código de ejemplo registra el servicio IMessageWriter
con el tipo concreto MessageWriter
. El método AddSingleton rregistra el servicio con una vigencia singleton, la vigencia de la app. Las duraciones del servicio se describen más adelante en este artículo.
using DependencyInjection.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
En el código anterior, la aplicación de ejemplo:
Crea una instancia del generador de aplicación de host.
Configura los servicios mediante el registro de:
Worker
como servicio hospedado. Para obtener más información, consulte Servicios de trabajo en .NET.- La interfaz
IMessageWriter
como servicio de singleton con una implementación correspondiente de la claseMessageWriter
.
Compila el host y lo ejecuta.
El host contiene el proveedor de servicios de inserción de dependencias. También contiene el resto de servicios pertinentes que se requieren para crear instancias de Worker
automáticamente y proporcionar la implementación de IMessageWriter
correspondiente como 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);
}
}
}
Mediante el uso del patrón de DI, el servicio de trabajo:
- No usa el tipo concreto
MessageWriter
, solo la interfazIMessageWriter
que lo implementa. Esto facilita el cambio de la implementación que el servicio de trabajo utiliza sin modificar ese servicio. - No crea una instancia de
MessageWriter
. El contenedor de DI es el que la crea.
La implementación de la interfaz de IMessageWriter
se puede mejorar mediante el uso de la API de registro integrada:
namespace DependencyInjection.Example;
public class LoggingMessageWriter(
ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
public void Write(string message) =>
logger.LogInformation("Info: {Msg}", message);
}
El método AddSingleton
actualizado registra la nueva implementación de IMessageWriter
:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
El método HostApplicationBuilder (builder
) forma parte del paquete NuGet Microsoft.Extensions.Hosting
.
LoggingMessageWriter
depende de ILogger<TCategoryName>, el que solicita en el constructor. ILogger<TCategoryName>
es un servicio proporcionado por el marco de trabajo.
No es raro usar la inserción de dependencias de forma encadenada. Cada dependencia solicitada a su vez solicita sus propias dependencias. El contenedor resuelve las dependencias del gráfico y devuelve el servicio totalmente resuelto. El conjunto colectivo de dependencias que deben resolverse suele denominarse árbol de dependencias, gráfico de dependencias o gráfico de objetos.
El contenedor resuelve ILogger<TCategoryName>
aprovechando las ventajas de los tipos abiertos (genéricos), lo que elimina la necesidad de registrar todos los tipos construidos (genéricos).
Con la terminología de inserción de dependencias, un servicio:
- Por lo general, es un objeto que proporciona un servicio a otros objetos, como el servicio
IMessageWriter
. - No está relacionado con un servicio web, aunque el servicio puede utilizar un servicio web.
El marco de trabajo proporciona un sistema de registro sólido. Las implementaciones de IMessageWriter
que se muestran en los ejemplos anteriores se escribieron para mostrar la inserción de DI básica, no para implementar el registro. La mayoría de las aplicaciones no deberían tener que escribir registradores. En el código siguiente se muestra cómo usar el registro predeterminado, que solo requiere que Worker
se registre como un servicio hospedado AddHostedService:
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);
}
}
}
Con el código anterior, no es necesario actualizar Program.cs porque el registro se proporciona a través del marco de trabajo.
Reglas de detección de varios constructores
Cuando un tipo define más de un constructor, el proveedor de servicios tiene lógica para determinar qué constructor usar. Se selecciona el constructor con el mayor número de parámetros donde los tipos pueden resolver DI. Considere el siguiente servicio de ejemplo de C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(FooService fooService, BarService barService)
{
// omitted for brevity
}
}
En el código anterior, suponga que el registro se ha agregado y se puede resolver desde el proveedor de servicios, pero no los tipos FooService
y BarService
. El constructor con el parámetro ILogger<ExampleService>
se usa para resolver la instancia de ExampleService
. Aunque hay un constructor que define más parámetros, los tipos FooService
y BarService
no se pueden resolver en DI.
Si hay ambigüedad al detectar constructores, se produce una excepción. Considere el siguiente servicio de ejemplo de C#:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(ILogger<ExampleService> logger)
{
// omitted for brevity
}
public ExampleService(IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Advertencia
El código ExampleService
con parámetros de tipo ambiguos que se pueden resolver en DI produciría una excepción. No haga esto, está pensado para mostrar lo que significan los "tipos ambiguos que se pueden resolver en DI".
En el ejemplo anterior, hay tres constructores. El primer constructor no tiene parámetros y no requiere ningún servicio del proveedor de servicios. Suponga que tanto el registro como las opciones se han agregado al contenedor de DI y son servicios que se pueden resolver en DI. Cuando el contenedor de DI intente resolver el tipo ExampleService
, producirá una excepción, ya que los dos constructores son ambiguos.
Puede evitar esta ambigüedad mediante la definición de un constructor que acepte tipos que se puedan resolver en DI:
public class ExampleService
{
public ExampleService()
{
}
public ExampleService(
ILogger<ExampleService> logger,
IOptions<ExampleOptions> options)
{
// omitted for brevity
}
}
Registro de grupos de servicios con métodos de extensión
Las extensiones de Microsoft usan una convención para registrar un grupo de servicios relacionados. La convención es usar un único método de extensión de Add{GROUP_NAME}
para registrar todos los servicios requeridos por una característica de marco. Por ejemplo, el método de extensión AddOptions registra todos los servicios necesarios para usar las opciones.
Servicios proporcionados por el marco de trabajo
Cuando se usa cualquiera de los patrones de host o generador de aplicaciones disponibles, se aplican los valores predeterminados y el marco registra los servicios. Considere algunos de los patrones de host y generador de aplicaciones más populares:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
Después de crear un generador a partir de cualquiera de estas API, IServiceCollection
tiene servicios definidos por el marco, en función de cómo se configuró el host. En el caso de las aplicaciones basadas en las plantillas de .NET, el marco de trabajo puede registrar cientos de servicios.
En la tabla siguiente se ilustra una pequeña muestra de estos servicios registrados por el marco:
Tipo de servicio | Período de duración |
---|---|
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> | Transitorio |
Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
Duraciones de servicios
Los servicios se pueden registrar con una de las duraciones siguientes:
En las secciones siguientes se describen cada una de las duraciones anteriores. Elija una duración adecuada para cada servicio registrado.
Transitorio
Los servicios de duración transitoria se crean cada vez que el contenedor del servicio los solicita. Para registrar un servicio como transitorio, llame a AddTransient.
En las aplicaciones que procesan solicitudes, los servicios transitorios se eliminan al final de la solicitud. Esta duración incurre en asignaciones por solicitud, ya que los servicios se resuelven y construyen cada vez. Para obtener más información, consulte Instrucciones de inserción de dependencias: Guía de IDisposable para instancias transitorias y compartidas.
Con ámbito
En el caso de las aplicaciones web, una duración con ámbito indica que los servicios se crean una vez por solicitud de cliente (conexión). Registre los servicios con ámbito con AddScoped.
En las aplicaciones que procesan solicitudes, los servicios con ámbito se eliminan al final de la solicitud.
Cuando se usa Entity Framework Core, el método de extensión AddDbContext registra tipos de DbContext
con una duración de ámbito de forma predeterminada.
Nota
No resuelva un servicio con ámbito desde un singleton y tenga cuidado de no hacerlo indirectamente, por ejemplo, a través de un servicio transitorio. Puede dar lugar a que el servicio adopte un estado incorrecto al procesar solicitudes posteriores. Basta con:
- Resolver un servicio singleton desde un servicio con ámbito o transitorio.
- Resolver un servicio con ámbito desde otro servicio con ámbito o transitorio.
De manera predeterminada, en el entorno de desarrollo, resolver un servicio desde otro servicio con una duración más larga genera una excepción. Para más información, vea Validación del ámbito.
Singleton
Los servicios de duración de singleton se crean de alguna de las formas siguientes:
- La primera vez que se solicitan.
- Mediante el desarrollador, al proporcionar una instancia de implementación directamente al contenedor. Este enfoque rara vez es necesario.
Cada solicitud siguiente de la implementación del servicio desde el contenedor de inserción de dependencias utiliza la misma instancia. Si la aplicación requiere un comportamiento de singleton, permita que el contenedor de servicios administre la duración del servicio. No implemente el modelo de diseño singleton y proporcione el código para desechar el singleton. Los servicios nunca deben desecharse mediante el código que haya resuelto el servicio del contenedor. Si un tipo o fábrica se registra como singleton, el contenedor elimina el singleton de manera automática.
Registre los servicios singleton con AddSingleton. Los servicios singleton deben ser seguros para los subprocesos y se suelen usar en servicios sin estado.
En las aplicaciones que procesan solicitudes, los servicios singleton se eliminan cuando ServiceProvider se elimina al cerrarse la aplicación. Como no se libera memoria hasta que se apaga la aplicación, se debe tener en cuenta el uso de memoria con un servicio singleton.
Métodos de registro del servicio
El marco proporciona métodos de extensión de registro del servicio que resultan útiles en escenarios específicos:
Método | Automático objeto eliminación |
Múltiple implementaciones |
Transferencia de argumentos |
---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>() Ejemplo: services.AddSingleton<IMyDep, MyDep>(); |
Sí | Sí | No |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Ejemplos: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
Sí | Sí | Sí |
Add{LIFETIME}<{IMPLEMENTATION}>() Ejemplo: services.AddSingleton<MyDep>(); |
Sí | No | No |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) Ejemplos: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); |
No | Sí | Sí |
AddSingleton(new {IMPLEMENTATION}) Ejemplos: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); |
No | No | Sí |
Para obtener más información sobre el tipo de eliminación, consulte la sección Eliminación de servicios.
El registro de un servicio con un solo tipo de implementación es equivalente al registro de ese servicio con la misma implementación y el mismo tipo de servicio. Por ejemplo, suponga el siguiente código:
services.AddSingleton<ExampleService>();
Esto equivale a registrar el servicio con el servicio y la implementación de los mismos tipos:
services.AddSingleton<ExampleService, ExampleService>();
Esta equivalencia es la razón por la que no se pueden registrar varias implementaciones de un servicio mediante los métodos que no toman un tipo de servicio explícito. Estos métodos pueden registrar varias instancias de un servicio, pero todos tienen el mismo tipo de implementación.
Cualquiera de los métodos de registro de servicio anteriores se puede usar para registrar varias instancias de servicio del mismo tipo de servicio. En el ejemplo siguiente se llama a AddSingleton
dos veces con IMessageWriter
como tipo de servicio. La segunda llamada a AddSingleton
invalida la anterior cuando se resuelve como IMessageWriter
, y se agrega a la anterior cuando varios servicios se resuelven mediante IEnumerable<IMessageWriter>
. Los servicios aparecen en el orden en que se han registrado al resolverse mediante 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();
El código fuente de ejemplo anterior registra dos implementaciones de 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);
}
}
ExampleService
define dos parámetros de constructor, un único IMessageWriter
y un IEnumerable<IMessageWriter>
. El IMessageWriter
único es la última implementación registrada, mientras que IEnumerable<IMessageWriter>
representa todas las implementaciones registradas.
El marco también proporciona métodos de extensión TryAdd{LIFETIME}
, que registran el servicio solo si todavía no hay registrada una implementación.
En el ejemplo siguiente, la llamada a AddSingleton
registra ConsoleMessageWriter
como una implementación para IMessageWriter
. La llamada a TryAddSingleton
no tiene ningún efecto porque IMessageWriter
ya tiene una implementación registrada:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
TryAddSingleton
no tiene ningún efecto, puesto que ya se agregó y "try" generará un error. ExampleService
impondría lo siguiente:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
Para más información, consulte:
Los métodos TryAddEnumerable(ServiceDescriptor) registran el servicio solo si todavía no hay una implementación del mismo tipo. A través de IEnumerable<{SERVICE}>
se resuelven varios servicios. Al registrar los servicios, agregue una instancia si no se ha agregado ya una del mismo tipo. Los autores de bibliotecas usan TryAddEnumerable
para evitar el registro de varias copias de una implementación en el contenedor.
En el ejemplo siguiente, la primera llamada a TryAddEnumerable
registra MessageWriter
como una implementación para IMessageWriter1
. La segunda llamada registra MessageWriter
para IMessageWriter2
. La tercera llamada no tiene ningún efecto porque IMessageWriter1
ya tiene una implementación 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>());
Normalmente, el registro del servicio es independiente del orden, excepto cuando se registran varias implementaciones del mismo tipo.
IServiceCollection
es una colección de objetos ServiceDescriptor. En el ejemplo siguiente se muestra cómo registrar un servicio mediante la creación e incorporación de un elemento ServiceDescriptor
:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
Los métodos Add{LIFETIME}
integrados usan el mismo enfoque. Por ejemplo, consulte el código fuente de AddScoped.
Comportamiento de inserción de constructor
Los servicios se pueden resolver mediante el uso de:
- IServiceProvider
- ActivatorUtilities:
- Crea objetos que no están registrados en el contenedor.
- Se utiliza con algunas características de Framework.
Los constructores pueden aceptar argumentos que no se proporcionan mediante la inserción de dependencias, pero los argumentos deben asignar valores predeterminados.
Cuando se resuelven los servicios mediante IServiceProvider
o ActivatorUtilities
, la inserción del constructor requiere un constructor público.
Cuando se resuelven los servicios mediante ActivatorUtilities
, la inserción del constructor requiere que exista solo un constructor aplicable. Se admiten las sobrecargas de constructor, pero solo puede existir una sobrecarga cuyos argumentos pueda cumplir la inserción de dependencias.
Validación del ámbito
Cuando la aplicación se ejecuta en el entorno Development
y llama a CreateApplicationBuilder para compilar el host, el proveedor de servicios predeterminado realiza comprobaciones para confirmar lo siguiente:
- Los servicios con ámbito no se resuelven desde el proveedor de servicios raíz.
- Los servicios con ámbito no se insertan en singletons.
El proveedor de servicios raíz se crea cuando se llama a BuildServiceProvider. La vigencia del proveedor de servicios raíz es la misma que la de la aplicación cuando el proveedor se inicia con la aplicación, y se elimina cuando la aplicación se cierra.
De la eliminación de los servicios con ámbito se encarga el contenedor que los creó. Si un servicio con ámbito se crea en el contenedor raíz, su duración sube a la del singleton, ya que solo lo puede eliminar el contenedor raíz cuando la aplicación se cierra. Al validar los ámbitos de servicio, este tipo de situaciones se detectan cuando se llama a BuildServiceProvider
.
Escenarios de ámbito
IServiceScopeFactory siempre se registra como singleton, pero IServiceProvider puede variar en función de la duración de la clase que lo contiene. Por ejemplo, si resuelve los servicios desde un ámbito y cualquiera de esos servicios toma un valor IServiceProvider, será una instancia con ámbito.
Para lograr los servicios de ámbito dentro de las implementaciones de IHostedService, como BackgroundService, no inyecte las dependencias del servicio a través de la inserción de constructores. En su lugar, inyecte una instancia de IServiceScopeFactory, cree un ámbito y, a continuación, resuelva las dependencias del ámbito para usar la duración de servicio adecuada.
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);
}
}
}
}
}
En el código anterior, mientras se ejecuta la aplicación, el servicio en segundo plano:
- Depende de IServiceScopeFactory.
- Crea una instancia de IServiceScope para resolver servicios adicionales.
- Resuelve los servicios con ámbito para su consumo.
- Trabaja en el procesamiento de objetos, los retransmite y, por último, los marca como procesados.
En el código fuente de ejemplo, puede ver cómo las implementaciones de IHostedService pueden beneficiarse de la duración del servicio con ámbito.
Servicios con claves
A partir de .NET 8, existe compatibilidad con el registro de servicios y las búsquedas basadas en una clave, lo que significa que es posible registrar varios servicios con una clave diferente y usar esta clave para la búsqueda.
Pongamos un caso en el que tiene implementaciones diferentes de la interfaz IMessageWriter
: MemoryMessageWriter
y QueueMessageWriter
.
Puede registrar estos servicios mediante la sobrecarga de los métodos de registro de servicio (vistos anteriormente) que admiten una clave como parámetro:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");
key
no se limita a string
, puede ser cualquier object
que desee, siempre y cuando el tipo implemente Equals
correctamente.
En el constructor de la clase que usa IMessageWriter
, agregue FromKeyedServicesAttribute para especificar la clave del servicio que se va a resolver:
public class ExampleService
{
public ExampleService(
[FromKeyedServices("queue")] IMessageWriter writer)
{
// Omitted for brevity...
}
}
Consulte también
- Descripción de los conceptos básicos de inserción de dependencias en .NET
- Uso de la inserción de dependencias en .NET
- Instrucciones para la inserción de dependencias
- Inserción de dependencias en ASP.NET Core
- Patrones de la Conferencia NDC para el desarrollo de aplicaciones de inserción de dependencias
- Principio de dependencias explícitas
- Los contenedores de inversión de control y el patrón de inserción de dependencias (Martin Fowler)
- Los errores de DI deben crearse en el repositorio github.com/dotnet/extensions.