tutoriales.com

C#: Explorando Inyección de Dependencias con IServiceCollection y Proveedores de Servicio

Este tutorial te guiará a través del concepto fundamental de la Inyección de Dependencias (DI) en C# y .NET Core, explorando cómo IServiceCollection y los proveedores de servicios se utilizan para construir aplicaciones modulares y mantenibles. Aprenderás las diferentes estrategias de tiempo de vida (scoped, singleton, transient) y cómo aplicarlas con ejemplos prácticos. Al finalizar, tendrás una comprensión sólida para aplicar DI en tus proyectos.

Intermedio25 min de lectura3 views
Reportar error

La Inyección de Dependencias (DI) es un patrón de diseño fundamental en el desarrollo de software moderno, especialmente relevante en el ecosistema de C# y .NET Core. Permite construir aplicaciones flexibles, escalables y fáciles de probar al desacoplar los componentes. En esencia, DI invierte la responsabilidad de cómo un objeto obtiene sus dependencias, en lugar de que el objeto las cree por sí mismo, se le inyectan externamente.

Este tutorial se centrará en cómo .NET Core implementa DI a través de IServiceCollection y los distintos tiempos de vida de los servicios, proporcionando ejemplos claros y explicaciones detalladas para que puedas dominar esta técnica esencial.

🚀 ¿Qué es la Inyección de Dependencias (DI)?

Imagina que tienes una clase ReportGenerator que necesita una base de datos para funcionar. Sin DI, ReportGenerator podría crear una instancia de DatabaseService directamente:

public class ReportGenerator
{
    private DatabaseService _databaseService;

    public ReportGenerator()
    {
        // ReportGenerator es responsable de crear DatabaseService
        _databaseService = new DatabaseService();
    }

    public void GenerateReport()
    {
        // Usa _databaseService
        _databaseService.GetData();
        Console.WriteLine("Reporte generado.");
    }
}

public class DatabaseService
{
    public void GetData()
    {
        Console.WriteLine("Obteniendo datos de la base de datos...");
    }
}

Este enfoque tiene problemas:

  • Acoplamiento Fuerte: ReportGenerator está fuertemente acoplado a DatabaseService. Si cambias DatabaseService (por ejemplo, usas otra base de datos), ReportGenerator también debe cambiar.
  • Dificultad para Probar: Para probar ReportGenerator, necesitas una instancia real de DatabaseService, lo que puede ser lento o requerir una base de datos real. Es difícil "simular" el DatabaseService.
  • Rigidez: Si DatabaseService necesita otras dependencias, ReportGenerator tendría que gestionarlas también.

La Inyección de Dependencias resuelve esto invirtiendo el control. En lugar de que ReportGenerator cree DatabaseService, se le pasa una instancia de DatabaseService (o una abstracción de este) cuando se crea ReportGenerator. La forma más común de hacerlo es a través del constructor.

public interface IDatabaseService
{
    void GetData();
}

public class DatabaseService : IDatabaseService
{
    public void GetData()
    {
        Console.WriteLine("Obteniendo datos de la base de datos...");
    }
}

public class ReportGenerator
{
    private readonly IDatabaseService _databaseService;

    public ReportGenerator(IDatabaseService databaseService)
    {
        // La dependencia se 'inyecta' a través del constructor
        _databaseService = databaseService;
    }

    public void GenerateReport()
    {
        _databaseService.GetData();
        Console.WriteLine("Reporte generado con DI.");
    }
}

Ahora, ReportGenerator no sabe cómo se crea IDatabaseService, solo sabe que necesita una instancia que implemente esa interfaz. Esto nos lleva a los beneficios clave:

  • Acoplamiento Débil: ReportGenerator depende de una abstracción (IDatabaseService), no de una implementación concreta. Esto facilita el cambio de implementaciones.
  • Testabilidad Mejorada: Puedes inyectar un MockDatabaseService (una implementación de IDatabaseService para pruebas) sin afectar el código de ReportGenerator.
  • Reusabilidad: Los componentes son más fáciles de reusar en diferentes contextos.
💡 Consejo: Siempre inyecta abstracciones (interfaces) en lugar de implementaciones concretas. Esto maximiza la flexibilidad y la testabilidad.

🎯 El Contenedor de Inversión de Control (IoC)

Entonces, ¿quién es el encargado de crear y pasar las dependencias? Aquí es donde entra en juego el Contenedor de Inversión de Control (IoC), también conocido como Contenedor DI. En .NET Core, el IoC Container es una parte integral del framework.

El contenedor DI es un framework que se encarga de:

  1. Registrar Tipos: Le dices al contenedor qué interfaz debe asociarse con qué implementación concreta (ej. IDatabaseService con DatabaseService).
  2. Resolver Dependencias: Cuando una clase requiere una dependencia (ej. ReportGenerator necesita IDatabaseService), el contenedor crea la instancia apropiada y la inyecta.
  3. Gestionar el Tiempo de Vida: El contenedor decide cuándo se crea una instancia, cuándo se reutiliza y cuándo se destruye (esto es crucial y lo veremos en detalle).
Aplicación / Clase Cliente (ReportGenerator) Contenedor IoC (IServiceProvider) Servicios (IDatabaseService, DatabaseService) 1. Solicita ReportGenerator 2. Identifica dependencias 3. Provee instancias 4. Inyecta y devuelve

✨ IServiceCollection y IServiceProvider en .NET Core

En .NET Core, la Inyección de Dependencias es una característica de primera clase. Los dos componentes clave para entender cómo funciona son IServiceCollection y IServiceProvider.

IServiceCollection

IServiceCollection es una colección de descriptores de servicio. Es donde registras tus servicios y sus dependencias al inicio de tu aplicación (normalmente en el método ConfigureServices de Startup.cs o en Program.cs en .NET 6+).

Piensa en IServiceCollection como una lista de recetas que le das al chef (el IServiceProvider). Cada receta describe cómo crear una instancia de un servicio determinado.

Aquí es donde usas métodos como AddTransient, AddScoped y AddSingleton para registrar tus servicios, que determinan su tiempo de vida.

IServiceProvider

IServiceProvider es el contenedor DI en sí mismo. Es el objeto responsable de resolver las dependencias, es decir, de crear y proporcionar las instancias de los servicios registrados cuando se solicitan.

Una vez que todos los servicios han sido registrados en IServiceCollection, se construye un IServiceProvider a partir de ella. Este IServiceProvider es el que realmente distribuye los servicios a tu aplicación.

// En un entorno de consola, para ilustrar:
using Microsoft.Extensions.DependencyInjection;

// 1. Crear IServiceCollection y registrar servicios
var services = new ServiceCollection();
services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddTransient<ReportGenerator>();

// 2. Construir el IServiceProvider
var serviceProvider = services.BuildServiceProvider();

// 3. Obtener una instancia de ReportGenerator, que resolverá IDatabaseService
var reportGenerator = serviceProvider.GetRequiredService<ReportGenerator>();
reportGenerator.GenerateReport();

// En aplicaciones ASP.NET Core, esto se gestiona automáticamente por el framework
📌 Nota: En aplicaciones ASP.NET Core (y .NET Generic Host), no necesitas llamar a `BuildServiceProvider()` explícitamente en `Program.cs` o `Startup.cs`. El framework lo hace por ti y te proporciona un `IServiceProvider` listo para usar.

🔄 Tiempos de Vida de los Servicios: Transient, Scoped y Singleton

El tiempo de vida de un servicio registrado con DI define cuándo se crea una instancia del servicio, cuánto tiempo existe y cuándo se destruye. Comprender los diferentes tiempos de vida es crucial para evitar problemas de rendimiento, inconsistencias de estado y fugas de memoria.

AddTransient()

  • Definición: AddTransient crea una nueva instancia del servicio cada vez que se solicita.
  • Uso Ideal: Para servicios ligeros y sin estado, o para servicios que necesitan ser completamente independientes de otras instancias.
  • Ejemplo: Operaciones únicas, servicios que realizan un solo trabajo o clases utilitarias sin estado.
CaracterísticaDescripción
InstanciaciónCada solicitud obtiene una nueva instancia
EstadoSin estado (o estado efímero dentro de la llamada)
Costo de creaciónBajo
MemoriaPotencialmente más uso si se solicita mucho
HilosSeguro para hilos (cada hilo obtiene su propia instancia)

Escenario: Un servicio de cálculo que procesa datos de forma independiente en cada llamada.

// Registro
services.AddTransient<ITransientService, TransientService>();

// Uso (cada vez que se pide, se crea uno nuevo)
var service1 = serviceProvider.GetRequiredService<ITransientService>();
var service2 = serviceProvider.GetRequiredService<ITransientService>();

Console.WriteLine($"Transient 1 Hash: {service1.GetHashCode()}"); // Hash único
Console.WriteLine($"Transient 2 Hash: {service2.GetHashCode()}"); // Hash único y diferente
⚠️ Advertencia: Ten cuidado al inyectar servicios `Transient` en servicios `Singleton` o `Scoped`. Podrías encontrarte con el problema de "captura" (_capturing_), donde una instancia `Transient` se mantiene viva por más tiempo de lo esperado debido a que la referencia es sostenida por el servicio de mayor duración.

AddScoped()

  • Definición: AddScoped crea una instancia del servicio una vez por alcance (scope). En el contexto de ASP.NET Core, un alcance generalmente corresponde a una solicitud HTTP.
  • Uso Ideal: Para servicios que necesitan mantener un estado por solicitud, como servicios de contexto de base de datos (DbContext) o servicios que encapsulan una transacción unitaria.
  • Ejemplo: Un servicio que gestiona la información del usuario actual para una solicitud web.
CaracterísticaDescripción
InstanciaciónUna vez por alcance (ej. por solicitud HTTP)
EstadoPuede mantener estado dentro del alcance
Costo de creaciónModerado (una vez por alcance)
MemoriaLiberado al finalizar el alcance
HilosSeguro para hilos dentro de un mismo alcance

Escenario: Un UserService que carga los detalles del usuario al inicio de una solicitud y los reutiliza durante toda esa solicitud.

// Registro
services.AddScoped<IScopedService, ScopedService>();

// Uso (simulando un alcance de solicitud)
using (var scope = serviceProvider.CreateScope())
{
    var scopedServiceProvider = scope.ServiceProvider;

    var serviceA = scopedServiceProvider.GetRequiredService<IScopedService>();
    var serviceB = scopedServiceProvider.GetRequiredService<IScopedService>();

    Console.WriteLine($"Scoped A Hash: {serviceA.GetHashCode()}"); // Mismo hash
    Console.WriteLine($"Scoped B Hash: {serviceB.GetHashCode()}"); // Mismo hash
}

using (var scope = serviceProvider.CreateScope())
{
    var scopedServiceProvider = scope.ServiceProvider;
    var serviceC = scopedServiceProvider.GetRequiredService<IScopedService>();
    Console.WriteLine($"Scoped C Hash (nuevo scope): {serviceC.GetHashCode()}"); // Nuevo hash, diferente a A y B
}
🔥 Importante: `DbContext` en Entity Framework Core se registra casi siempre como `Scoped` para asegurar que cada solicitud HTTP obtenga su propia instancia y evitar problemas de concurrencia y seguimiento de cambios.

AddSingleton()

  • Definición: AddSingleton crea una única instancia del servicio para toda la vida de la aplicación.
  • Uso Ideal: Para servicios que son costosos de crear, que no tienen estado o que necesitan compartir un estado global a través de toda la aplicación.
  • Ejemplo: Configuraciones de aplicación, caches de datos, servicios de logging, o servicios de autenticación que no dependen del contexto de una solicitud específica.
CaracterísticaDescripción
InstanciaciónUna única instancia para toda la vida de la aplicación
EstadoPuede mantener estado global (¡cuidado con la concurrencia!)
Costo de creaciónAlto (se crea una sola vez al inicio)
MemoriaPersiste durante toda la vida de la app
HilosNo seguro para hilos si mantiene estado modificable. Requiere sincronización explícita.

Escenario: Un servicio de configuración que carga parámetros al inicio de la aplicación y los sirve a todas las partes que los necesiten.

// Registro
services.AddSingleton<ISingletonService, SingletonService>();

// Uso (siempre la misma instancia)
var serviceX = serviceProvider.GetRequiredService<ISingletonService>();
var serviceY = serviceProvider.GetRequiredService<ISingletonService>();

Console.WriteLine($"Singleton X Hash: {serviceX.GetHashCode()}"); // Mismo hash
Console.WriteLine($"Singleton Y Hash: {serviceY.GetHashCode()}"); // Mismo hash
⚠️ Advertencia: Los servicios `Singleton` que manejan estado *modificable* deben ser diseñados para ser seguros para hilos (thread-safe) o ser inmutables, ya que múltiples solicitudes o hilos pueden acceder a la misma instancia simultáneamente.

🛠️ Implementación Práctica en ASP.NET Core

Vamos a ver cómo se aplican estos conceptos en un proyecto real de ASP.NET Core. Crearemos una API simple y registraremos servicios con diferentes tiempos de vida.

Paso 1: Crear un nuevo proyecto ASP.NET Core Web API

Abra Visual Studio o use la CLI de .NET:

dotnet new webapi -n DependencyInjectionDemo
cd DependencyInjectionDemo

Paso 2: Definir las interfaces y las implementaciones de los servicios

Cree una nueva carpeta Services y dentro de ella, los siguientes archivos:

ITimeService.cs

namespace DependencyInjectionDemo.Services;

public interface ITimeService
{
    string GetCurrentTime();
}

TransientTimeService.cs

namespace DependencyInjectionDemo.Services;

public class TransientTimeService : ITimeService
{
    private readonly Guid _id;

    public TransientTimeService()
    {
        _id = Guid.NewGuid();
    }

    public string GetCurrentTime()
    {
        return $"Transient Time: {DateTime.Now:HH:mm:ss.fff} - ID: {_id}";
    }
}

ScopedTimeService.cs

namespace DependencyInjectionDemo.Services;

public class ScopedTimeService : ITimeService
{
    private readonly Guid _id;

    public ScopedTimeService()
    {
        _id = Guid.NewGuid();
    }

    public string GetCurrentTime()
    {
        return $"Scoped Time: {DateTime.Now:HH:mm:ss.fff} - ID: {_id}";
    }
}

SingletonTimeService.cs

namespace DependencyInjectionDemo.Services;

public class SingletonTimeService : ITimeService
{
    private readonly Guid _id;

    public SingletonTimeService()
    {
        _id = Guid.NewGuid();
    }

    public string GetCurrentTime()
    {
        return $"Singleton Time: {DateTime.Now:HH:mm:ss.fff} - ID: {_id}";
    }
}

Paso 3: Registrar los servicios en Program.cs

Modifique el archivo Program.cs para registrar estos servicios con sus respectivos tiempos de vida:

using DependencyInjectionDemo.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Registro de servicios con diferentes tiempos de vida
builder.Services.AddTransient<ITimeService, TransientTimeService>();
builder.Services.AddScoped<ITimeService, ScopedTimeService>();
builder.Services.AddSingleton<ITimeService, SingletonTimeService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
📌 Nota: Hemos registrado `ITimeService` tres veces con diferentes implementaciones. Esto es solo para fines de demostración. En una aplicación real, probablemente tendrías interfaces y clases distintas para cada propósito. Por ejemplo, `ITransientService`, `IScopedService`, `ISingletonService`. Aquí, el framework usa la *última* implementación registrada para una interfaz si no se especifica un nombre o una clave. Sin embargo, en un controlador, inyectaremos por tipo concreto para poder ver los 3 a la vez.

Paso 4: Crear un controlador para probar los servicios

Modifique el controlador WeatherForecastController.cs o cree uno nuevo llamado TimeController.cs.

TimeController.cs

using DependencyInjectionDemo.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionDemo.Controllers;

[ApiController]
[Route("[controller]")]
public class TimeController : ControllerBase
{
    private readonly TransientTimeService _transientService1;
    private readonly TransientTimeService _transientService2;
    private readonly ScopedTimeService _scopedService1;
    private readonly ScopedTimeService _scopedService2;
    private readonly SingletonTimeService _singletonService1;
    private readonly SingletonTimeService _singletonService2;

    public TimeController(
        TransientTimeService transientService1,
        TransientTimeService transientService2,
        ScopedTimeService scopedService1,
        ScopedTimeService scopedService2,
        SingletonTimeService singletonService1,
        SingletonTimeService singletonService2)
    {
        _transientService1 = transientService1;
        _transientService2 = transientService2;
        _scopedService1 = scopedService1;
        _scopedService2 = scopedService2;
        _singletonService1 = singletonService1;
        _singletonService2 = singletonService2;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var results = new List<string>
        {
            _transientService1.GetCurrentTime(),
            _transientService2.GetCurrentTime(),
            _scopedService1.GetCurrentTime(),
            _scopedService2.GetCurrentTime(),
            _singletonService1.GetCurrentTime(),
            _singletonService2.GetCurrentTime()
        };

        return Ok(results);
    }
}

Aquí, estamos inyectando dos instancias de cada tipo para demostrar sus comportamientos.

Paso 5: Ejecutar la aplicación y observar los resultados

Ejecute la aplicación (dotnet run o desde Visual Studio) y navegue a la URL del TimeController (ej. https://localhost:XXXX/Time).

Resultados esperados de una solicitud (GET /Time):

[
  "Transient Time: 10:30:01.123 - ID: 0a1b2c3d-....-0001",
  "Transient Time: 10:30:01.123 - ID: e4f5g6h7-....-0002",
  "Scoped Time: 10:30:01.123 - ID: 11223344-....-abcd",
  "Scoped Time: 10:30:01.123 - ID: 11223344-....-abcd",
  "Singleton Time: 10:30:01.123 - ID: ffgg7788-....-99aa",
  "Singleton Time: 10:30:01.123 - ID: ffgg7788-....-99aa"
]

Observaciones:

  • Transient: Las dos instancias _transientService1 y _transientService2 tienen IDs diferentes. Esto confirma que se crea una nueva instancia cada vez que se solicita.
  • Scoped: Las dos instancias _scopedService1 y _scopedService2 tienen el mismo ID. Esto es porque ambas se solicitaron dentro del mismo alcance (la misma solicitud HTTP). Si hicieras otra solicitud HTTP, verías un nuevo ID de alcance diferente al anterior.
  • Singleton: Las dos instancias _singletonService1 y _singletonService2 tienen el mismo ID. Este ID será el mismo para todas las solicitudes durante la vida de la aplicación.
Transient Scoped Singleton Registro IServiceCollection Registro IServiceCollection Registro IServiceCollection S1: Nueva Instancia Uso Disposición S2: Nueva Instancia Uso Disposición Inicio S1: Nueva Instancia Uso (Compartido en S1) Fin S1: Disposición Inicio S2: Nueva Instancia Uso (Compartido en S2) Fin S2: Disposición Inicio App: Nueva Instancia Uso a lo largo de todas las solicitudes Fin App: Disposición S1 = Solicitud HTTP 1 | S2 = Solicitud HTTP 2

💡 Patrones Avanzados y Consideraciones

Inyección de IEnumerable<T>

Puedes inyectar una colección de todas las implementaciones registradas para una interfaz específica. Esto es útil para implementar el patrón de estrategia o para tener múltiples manejadores para un evento.

// En Program.cs
builder.Services.AddTransient<INotificationSender, EmailSender>();
builder.Services.AddTransient<INotificationSender, SmsSender>();

// En una clase cliente:
public class NotificationProcessor
{
    private readonly IEnumerable<INotificationSender> _senders;

    public NotificationProcessor(IEnumerable<INotificationSender> senders)
    {
        _senders = senders;
    }

    public void SendNotifications(string message)
    {
        foreach (var sender in _senders)
        {
            sender.Send(message);
        }
    }
}

Fábricas de Servicios (AddFactory) o Funciones de Fábrica

Para escenarios más complejos donde la creación de un servicio depende de lógica en tiempo de ejecución (ej. un parámetro), puedes usar funciones de fábrica.

// En Program.cs
builder.Services.AddTransient<IDatabaseConnection>(serviceProvider =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    var connectionString = config.GetConnectionString("DefaultConnection");
    return new SqlServerConnection(connectionString);
});

Inyección de Dependencias y IHttpClientFactory

En .NET Core, IHttpClientFactory es la forma recomendada de trabajar con HttpClient. IHttpClientFactory se integra perfectamente con DI y resuelve problemas comunes como el agotamiento de sockets. Debes registrar tus HttpClients usando AddHttpClient.

// En Program.cs
builder.Services.AddHttpClient<IMyApiService, MyApiService>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// En MyApiService.cs
public class MyApiService : IMyApiService
{
    private readonly HttpClient _httpClient;

    public MyApiService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetDataAsync()
    {
        return await _httpClient.GetStringAsync("data");
    }
}

Evitar la "Service Location" Anti-patrón

Aunque IServiceProvider puede usarse para resolver servicios manualmente (serviceProvider.GetRequiredService<T>()), esto debe evitarse en el código de aplicación principal. Se conoce como el anti-patrón "Service Location". La inyección a través del constructor es preferible porque hace que las dependencias sean explícitas y facilita la prueba.

¿Cuándo es aceptable usar Service Location? En unos pocos escenarios específicos, puede ser necesario usar `IServiceProvider` directamente:
  • En la raíz de la aplicación: Por ejemplo, en el método Main o Program.cs para obtener el punto de entrada de la aplicación.
  • Middleware o filtros: Donde la inyección de constructor directo puede no ser posible o deseable para cada dependencia.
  • Fábricas personalizadas: Cuando necesitas crear objetos complejos y sus dependencias de forma dinámica.

Incluso en estos casos, es preferible inyectar un IServiceProvider (o IServiceScopeFactory) y luego crear un nuevo alcance (CreateScope()) para resolver los servicios dentro de él, especialmente para servicios Scoped y Transient.

✅ Buenas Prácticas y Consejos

  • Principio de Responsabilidad Única (SRP): Cada clase debe tener una sola razón para cambiar. DI te ayuda a lograrlo al inyectar las dependencias, manteniendo tus clases enfocadas.
  • Principio de Inversión de Dependencias (DIP): Depende de abstracciones, no de implementaciones concretas. Usa interfaces siempre que sea posible para tus servicios.
  • Evita el anti-patrón "Constructor Over-Injection": Si una clase tiene demasiadas dependencias en su constructor, es una señal de que probablemente tiene demasiadas responsabilidades y debería ser refactorizada. Más de 3-5 dependencias es una señal de alerta.
  • Entiende los Tiempos de Vida: Asegúrate de que el tiempo de vida de un servicio sea apropiado para su uso. Inyectar un servicio Scoped o Transient en un Singleton puede llevar a errores sutiles (el problema de captura), ya que la instancia Singleton retendrá la referencia a la instancia de vida más corta más allá de su alcance previsto.
  • Disponibilidad de IDisposable: Los servicios que implementan IDisposable serán correctamente gestionados y eliminados por el contenedor DI cuando su alcance finalice (para Scoped y Transient) o cuando la aplicación se detenga (para Singleton).
  • Registra solo lo necesario: No registres todas las clases de tu aplicación si no se van a inyectar. Esto puede reducir el tiempo de inicio y el uso de memoria.
💡 Consejo: Usa herramientas como el Visualizador de Gráficos de Dependencias (si tu IDE lo soporta) para entender cómo se resuelven tus servicios y detectar posibles problemas de tiempo de vida.

📝 Resumen de Tiempos de Vida

Tiempo de VidaCuándo se creaCuándo se eliminaUso Típico
TransientCada vez que se solicitaAl final de la solicitud/alcance que lo pidióServicios ligeros sin estado, operaciones únicas
ScopedUna vez por alcance (ej. solicitud HTTP)Al finalizar el alcance (ej. fin de solicitud)DbContext, servicios por solicitud con estado
SingletonUna única vez al inicio de la aplicaciónAl apagarse la aplicaciónIConfiguration, caches, loggers, servicios globales
Tutorial Completado

¡Felicidades! Has llegado al final de este tutorial sobre Inyección de Dependencias en C# y .NET Core. Ahora tienes una base sólida para entender y aplicar este patrón esencial en tus proyectos.

Preguntas Frecuentes:

¿Por qué la DI es tan importante para la testabilidad? Porque permite reemplazar fácilmente las dependencias reales por *mocks* o *stubs* (versiones simuladas) en las pruebas unitarias. Por ejemplo, en lugar de una `IDatabaseService` real que interactúa con una base de datos, puedes inyectar un `MockDatabaseService` que devuelve datos predefinidos, haciendo que tus pruebas sean rápidas, aisladas y deterministas.
¿Puedo usar mi propio contenedor DI en .NET Core? Sí, .NET Core está diseñado para ser extensible. Puedes reemplazar el contenedor DI predeterminado por uno de terceros como Autofac, Ninject, o DryIoc. Esto se hace llamando a `UseServiceProviderFactory` en tu `HostBuilder`.
¿Qué es el problema de "captura" (_capturing_)? Ocurre cuando un servicio con un tiempo de vida más largo (ej. `Singleton`) inyecta y retiene una referencia a un servicio con un tiempo de vida más corto (ej. `Scoped` o `Transient`). Esto significa que la instancia de vida más corta vivirá tanto como la `Singleton`, lo cual puede llevar a problemas de estado, recursos no liberados o comportamientos inesperados, ya que la instancia `Scoped` o `Transient` no se recreará como se espera en cada nuevo alcance o solicitud.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!

C#: Explorando Inyección de Dependencias con IServiceCollection y Proveedores de Servicio | tutoriales.com