AddTransient, AddScoped y AddSingleton: Cómo no confundirse con los ciclos de vida de servicios en C#
\n\n¿Alguna vez te has sorprendido pensando: «¿Por qué mi servicio guarda datos entre solicitudes, aunque no se lo pedí?» ¿O al contrario: «¿Por qué cada vez se crea un nuevo objeto y pierdo el estado?»
\n\n¡Bienvenido al mundo de la inyección de dependencias (Dependency Injection, DI) en ASP.NET Core! Este es uno de los temas más poderosos y a la vez más confusos para los principiantes. Hoy analizaremos los tres pilares sobre los que se basa la gestión de objetos en C#: AddTransient, AddScoped y AddSingleton.
Al final de este artículo, no solo entenderás la diferencia, sino que también podrás responder con confianza esta pregunta en una entrevista (¡sí, les encanta hacerla!). ¡Allá vamos!
\n\n¿Qué es el ciclo de vida de un servicio y para qué sirve?
\n\nImagina que tu aplicación es un restaurante. Los servicios son los cocineros, meseros y la vajilla. El ciclo de vida determina cuánto tiempo «viven»:
\n\n- \n
- Transient (Transitorio) — un empleado desechable. Llega, prepara un plato de sopa y se va. Nuevo pedido, nuevo empleado. \n
- Scoped (Con ámbito) — un empleado para un turno. Atiende a todos los clientes mientras el restaurante está abierto (mientras dura la solicitud HTTP). \n
- Singleton (Único) — el chef principal, que trabaja durante toda la vida del restaurante. Uno solo para toda la aplicación. \n
En C#, esta lógica se implementa a través del contenedor DI incorporado. Veamos el código.
\n\n1. AddTransient — una nueva instancia cada vez
\n\nCuándo usarlo: Servicios ligeros sin estado (stateless) que no almacenan datos entre llamadas. Ideal para repositorios, helpers, utilidades.
\n\nCómo funciona: Cada vez que solicitas el servicio (a través del constructor o directamente desde el contenedor), se crea un nuevo objeto.
\n\n// Registro en Program.cs\nbuilder.Services.AddTransient<IMyService, MyService>();\n\n// Ejemplo de servicio\npublic interface IMyService\n{\n Guid GetInstanceId();\n}\n\npublic class MyService : IMyService\n{\n private readonly Guid _id = Guid.NewGuid();\n public Guid GetInstanceId() => _id;\n}\n\n// Controlador que usa el servicio\npublic class HomeController : Controller\n{\n private readonly IMyService _service1;\n private readonly IMyService _service2;\n\n public HomeController(IMyService service1, IMyService service2)\n {\n _service1 = service1;\n _service2 = service2;\n }\n\n public IActionResult Index()\n {\n ViewBag.Id1 = _service1.GetInstanceId();\n ViewBag.Id2 = _service2.GetInstanceId();\n // Id1 e Id2 son GUID DIFERENTES\n return View();\n }\n}\n\n\nImportante: Incluso dentro de una misma solicitud HTTP, si inyectas IMyService en dos lugares diferentes, obtendrás dos objetos distintos. Se consume más memoria, pero no hay riesgo de «contaminación» de datos.
2. AddScoped — una instancia por solicitud
\n\nCuándo usarlo: Servicios que deben ser coherentes dentro de una misma solicitud HTTP. Por ejemplo, DbContext en Entity Framework Core, servicios de carrito de compras, registradores de solicitudes.
Cómo funciona: Dentro de una misma solicitud HTTP (o ámbito) se crea una sola instancia. Si la solicitas varias veces, obtienes el mismo objeto. Pero cada nueva solicitud de otro usuario obtendrá su propia instancia.
\n\n// Registro\nbuilder.Services.AddScoped<IScopedService, ScopedService>();\n\npublic interface IScopedService\n{\n Guid GetInstanceId();\n}\n\npublic class ScopedService : IScopedService\n{\n private readonly Guid _id = Guid.NewGuid();\n public Guid GetInstanceId() => _id;\n}\n\n// En el mismo controlador\npublic