{"id":197,"date":"2026-03-05T08:37:05","date_gmt":"2026-03-05T12:37:05","guid":{"rendered":"https:\/\/juredev.com\/blog\/?p=197"},"modified":"2026-03-05T09:01:31","modified_gmt":"2026-03-05T13:01:31","slug":"12-reglas-practicas-inyeccion-de-dependencias-en-asp-net-core","status":"publish","type":"post","link":"https:\/\/juredev.com\/blog\/2026\/03\/12-reglas-practicas-inyeccion-de-dependencias-en-asp-net-core\/","title":{"rendered":"12 Reglas Pr\u00e1cticas para Sacarle el M\u00e1ximo Provecho a la Inyecci\u00f3n de Dependencias en ASP.NET Core"},"content":{"rendered":"\n<p>La <a href=\"https:\/\/es.wikipedia.org\/wiki\/Inyecci%C3%B3n_de_dependencias\">inyecci\u00f3n de dependencias (DI)<\/a> en ASP.NET Core no es un accesorio: es un ciudadano de primera clase. El contenedor viene integrado desde el primer d\u00eda y forma parte del arranque mismo de la aplicaci\u00f3n.<\/p>\n\n\n\n<p>Cuando la usamos bien, conseguimos aplicaciones mucho m\u00e1s f\u00e1ciles de:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mantener<\/li>\n\n\n\n<li>Probar<\/li>\n\n\n\n<li>Entender (separaci\u00f3n de responsabilidades)<\/li>\n\n\n\n<li>Escalar<\/li>\n<\/ul>\n\n\n\n<p>Pero cuando la usamos mal\u2026 los problemas pueden ser silenciosos y caros: fugas de memoria, race conditions dif\u00edciles de reproducir, dependencias circulares, servicios que terminan fuertemente acoplados sin que nos demos cuenta.<\/p>\n\n\n\n<p>En este post te comparto 12 reglas pr\u00e1cticas que he visto que marcan la diferencia en proyectos reales (y que intento seguir yo mismo para no sufrir despu\u00e9s).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Primero, entendamos los lifetimes (muy r\u00e1pido)<\/h2>\n\n\n\n<p><a href=\"https:\/\/es.wikipedia.org\/wiki\/ASP.NET_Core\">ASP.NET Core<\/a> maneja tres ciclos de vida principales, y el orden importa much\u00edsimo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Singleton \u2192 Scoped \u2192 Transient<\/code><\/pre>\n\n\n\n<p>Es decir: un servicio de mayor duraci\u00f3n no deber\u00eda depender de uno que viva menos tiempo.<\/p>\n\n\n\n<p><strong>Regla de oro<\/strong>: un servicio solo deber\u00eda depender de servicios que tengan el <strong>mismo o menor tiempo de vida<\/strong> que \u00e9l en esa jerarqu\u00eda. Si rompes esta regla aparece el cl\u00e1sico problema de captive dependency (dependencia cautiva), y suele doler bastante cuando te das cuenta en producci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Transient para servicios livianos y sin estado<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>services.AddTransient&lt;IEmailFormatter, EmailFormatter>();<\/code><\/pre>\n\n\n\n<p>Cu\u00e1ndo elegir Transient:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Helpers, formateadores, mappers<\/li>\n\n\n\n<li>Servicios puramente funcionales (sin estado)<\/li>\n\n\n\n<li>Operaciones r\u00e1pidas y baratas<\/li>\n<\/ul>\n\n\n\n<p>Cada vez que alguien lo pide, se crea una instancia nueva.<\/p>\n\n\n\n<p><strong>Cuidado<\/strong>: si el constructor hace trabajo pesado (lectura de archivos grandes, conexiones iniciales costosas, etc.) o si se resuelve cientos de veces por request\u2026 pi\u00e9nsalo dos veces.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Scoped para todo lo que deba vivir \u201cdurante una petici\u00f3n HTTP\u201d<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>services.AddScoped&lt;IOrderService, OrderService>();<\/code><\/pre>\n\n\n\n<p>El caso estrella es el DbContext de Entity Framework Core:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>services.AddDbContext&lt;AppDbContext>();<\/code><\/pre>\n\n\n\n<p><strong>Scoped <\/strong>asegura que durante toda la request HTTP tendr\u00e1s <strong>la misma instancia<\/strong>. Esto es clave para mantener coherencia en transacciones, change tracking, etc.<\/p>\n\n\n\n<p><strong>Nunca <\/strong>registres un <code>DbContext<\/code> como Singleton. Es una de las formas m\u00e1s r\u00e1pidas de romper una aplicaci\u00f3n en producci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Singleton solo cuando realmente es seguro<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>services.AddSingleton&lt;ICacheService, MemoryCacheService>();<\/code><\/pre>\n\n\n\n<p>Un Singleton vive <strong>toda la vida de la aplicaci\u00f3n<\/strong>. Por lo tanto:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tiene que ser <strong>thread-safe<\/strong> (o sincronizar muy bien lo que toque)<\/li>\n\n\n\n<li>No debe guardar estado espec\u00edfico de un usuario o de una request<\/li>\n\n\n\n<li><strong>Jam\u00e1s<\/strong> debe depender de servicios Scoped<\/li>\n<\/ul>\n\n\n\n<p>Un buen candidato a Singleton suele ser un servicio completamente stateless o uno que maneja recursos compartidos de forma segura (por ejemplo caching).<\/p>\n\n\n\n<p>Si metes un Singleton que no es thread-safe o que captura dependencias m\u00e1s cortas\u2026 prep\u00e1rate para dolores de cabeza intermitentes que son infernales de debuggear.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. Siempre inyecta interfaces, nunca implementaciones concretas<\/h3>\n\n\n\n<p>Bien:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class OrderService : IOrderService\n{\n    private readonly IPaymentGateway _gateway;\n\n    public OrderService(IPaymentGateway gateway)\n    {\n        _gateway = gateway;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Mal (ev\u00edtalo):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public OrderService(PaymentGateway gateway) { \u2026 }<\/code><\/pre>\n\n\n\n<p>Inyectar la interfaz reduce el acoplamiento y te permite hacer mocks f\u00e1cilmente en pruebas unitarias. Es una de las bases del \u201cpor qu\u00e9 usamos DI\u201d en primer lugar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Si el constructor parece una lista de supermercado, algo huele mal<\/h3>\n\n\n\n<p>Ejemplo sospechoso:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public OrderService(\n    IRepository repo,\n    ILogger logger,\n    IMapper mapper,\n    ICache cache,\n    IValidator validator,\n    IEmailService email)\n{ \u2026 }<\/code><\/pre>\n\n\n\n<p>Cuando un constructor recibe 5\u20137 dependencias (o m\u00e1s), casi siempre est\u00e1s violando el principio de responsabilidad \u00fanica (SRP).<\/p>\n\n\n\n<p>Antes de a\u00f1adir la siguiente dependencia, p\u00e1rate y preg\u00fantate:<\/p>\n\n\n\n<p>\u201c\u00bfEsta clase est\u00e1 haciendo demasiadas cosas? \u00bfPodr\u00eda dividirla en dos servicios m\u00e1s peque\u00f1os y enfocados?\u201d<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6. Nunca, jam\u00e1s, inyectes un servicio Scoped dentro de un Singleton<\/h3>\n\n\n\n<p>Este es probablemente el error #1 que veo en revisiones de c\u00f3digo y en producci\u00f3n.<\/p>\n\n\n\n<p>Si un Singleton captura un Scoped, rompes la jerarqu\u00eda de lifetimes &#8211;> captive dependency.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>En el mejor caso &#8211;> excepci\u00f3n al arrancar (si tienes validaci\u00f3n activada)<\/li>\n\n\n\n<li>En el peor caso &#8211;> datos obsoletos, fugas de memoria o comportamiento impredecible que solo aparece con carga real<\/li>\n<\/ul>\n\n\n\n<p>Ev\u00edtalo a toda costa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7. Cuando de verdad necesites un Scoped desde un Singleton\u2026 usa IServiceScopeFactory<\/h3>\n\n\n\n<p>T\u00edpico en <code>BackgroundService<\/code>, Hosted Services, procesadores en background, etc.:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class Worker : BackgroundService\n{\n    private readonly IServiceScopeFactory _scopeFactory;\n\n    public Worker(IServiceScopeFactory scopeFactory)\n    {\n        _scopeFactory = scopeFactory;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        using var scope = _scopeFactory.CreateScope();\n        var dbContext = scope.ServiceProvider.GetRequiredService&lt;AppDbContext>();\n\n        \/\/ tu l\u00f3gica aqu\u00ed, dentro del scope correcto\n    }\n}<\/code><\/pre>\n\n\n\n<p>De esta forma creas un scope \u00abartificial\u00bb solo para esa ejecuci\u00f3n y todo queda en su lifetime correcto.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8. No dejes que Program.cs se convierta en un monstruo de 300 l\u00edneas<\/h3>\n\n\n\n<p>En lugar de registrar todo ah\u00ed, organiza con m\u00e9todos de extensi\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>builder.Services.AddInfrastructureServices();\nbuilder.Services.AddApplicationServices();\nbuilder.Services.AddDomainServices();<\/code><\/pre>\n\n\n\n<p>Ejemplo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public static class ServiceCollectionExtensions\n{\n    public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)\n    {\n        services.AddScoped&lt;IRepository, Repository>();\n        services.AddSingleton&lt;ICacheService, MemoryCacheService>();\n        \/\/ ...\n        return services;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Tu <code>Program.cs<\/code> queda limpio, legible y mucho m\u00e1s f\u00e1cil de mantener cuando el proyecto crece.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9. Activa la validaci\u00f3n del contenedor (al menos en desarrollo)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>builder.Host.UseDefaultServiceProvider(options =>\n{\n    options.ValidateScopes = true;\n    options.ValidateOnBuild = true;\n});<\/code><\/pre>\n\n\n\n<p>Esto detecta autom\u00e1ticamente:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Dependencias circulares<\/li>\n\n\n\n<li>Captive dependencies<\/li>\n\n\n\n<li>Servicios registrados mal<\/li>\n<\/ul>\n\n\n\n<p>Es infinitamente mejor que la aplicaci\u00f3n te avise al arrancar en local que descubrirlo a las 3 de la ma\u00f1ana en producci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">10. Olv\u00eddate del Service Locator (GetService \/ GetRequiredService fuera del constructor)<\/h3>\n\n\n\n<p>Mal (muy mal):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var service = provider.GetService&lt;IOrderService>();<\/code><\/pre>\n\n\n\n<p>Esto esconde las dependencias reales, complica much\u00edsimo las pruebas y va en contra de la filosof\u00eda de DI.<\/p>\n\n\n\n<p><strong>Siempre<\/strong> que puedas: inyecci\u00f3n por constructor. Es m\u00e1s expl\u00edcito y el compilador te ayuda.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">11. Caza las dependencias circulares desde el minuto uno<\/h3>\n\n\n\n<p>Si A necesita B y B necesita A\u2026 el contenedor se ahoga y no puede construir el grafo.<\/p>\n\n\n\n<p>Suele ser se\u00f1al de que el dise\u00f1o necesita revisi\u00f3n. Algunas salidas r\u00e1pidas:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Extraer la responsabilidad compartida a un tercer servicio<\/li>\n\n\n\n<li>Crear una interfaz \u201cpuente\u201d o evento<\/li>\n\n\n\n<li>Usar patrones como Mediator \/ CQRS cuando el dominio lo justifique<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">12. Usa IOptions (y sus variantes) para configuraciones fuertemente tipadas<\/h3>\n\n\n\n<p>En vez de inyectar <code>IConfiguration<\/code> directamente y hacer <code>.GetSection(\"MySettings\")<\/code> por todos lados:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class MyService\n{\n    private readonly MySettings _settings;\n\n    public MyService(IOptions&lt;MySettings> options)\n    {\n        _settings = options.Value;\n    }\n}<\/code><\/pre>\n\n\n\n<p>Variantes \u00fatiles seg\u00fan el caso:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>IOptions<\/code> &#8211;> configuraci\u00f3n est\u00e1tica (la m\u00e1s com\u00fan)<\/li>\n\n\n\n<li><code>IOptionsSnapshot<\/code> &#8211;> se refresca por cada request (\u00fatil si necesitas cambios sin reiniciar)<\/li>\n\n\n\n<li><code>IOptionsMonitor<\/code> &#8211;> singleton que permite suscribirse a cambios en vivo<\/li>\n<\/ul>\n\n\n\n<p>Tipar la configuraci\u00f3n mejora much\u00edsimo la legibilidad y reduce errores tontos de nombres de claves.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Errores que m\u00e1s veo repetirse en producci\u00f3n (resumen r\u00e1pido)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Singletons que capturan Scoped (el cl\u00e1sico)<\/li>\n\n\n\n<li>Transient en objetos pesados que se crean 1000 veces por segundo<\/li>\n\n\n\n<li>Clases \u201cdios\u201d con 12 dependencias y 400 l\u00edneas<\/li>\n\n\n\n<li>Uso de Service Locator \u201cporque es m\u00e1s r\u00e1pido de escribir\u201d<\/li>\n\n\n\n<li>DbContext como Singleton (por favor, no)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Recapitulemos para finalizar<\/h2>\n\n\n\n<p>La inyecci\u00f3n de dependencias no es solo \u00abuna forma cool de evitar new\u00bb. Es una de las palancas m\u00e1s potentes que tienes para que tu c\u00f3digo sea mantenible, testable y escalable a medida que el proyecto crece.<\/p>\n\n\n\n<p>Si respetas los lifetimes, mantienes las clases enfocadas, evitas atajos peligrosos y validas temprano\u2026 vas a tener aplicaciones mucho m\u00e1s sanas y f\u00e1ciles de evolucionar.<\/p>\n\n\n\n<p>La DI bien usada pasa desapercibida.<br>La DI mal usada\u2026 se convierte en un dolor constante.<\/p>\n\n\n\n<p>Mejor dejarla bien hecha desde el principio.<\/p>\n\n\n\n<p>\u00bfCu\u00e1l de estas 12 reglas te ha dolido m\u00e1s en alg\u00fan proyecto? \u00bfO cu\u00e1l aplicas religiosamente? Si quieres, d\u00e9jame un comentario me encantar\u00eda leer experiencias reales.<\/p>\n\n\n\n<p>Sigamos codificando.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>La inyecci\u00f3n de dependencias (DI) en ASP.NET Core no es un accesorio: es un ciudadano de primera clase. El contenedor viene integrado desde el primer d\u00eda y forma parte del arranque mismo de la aplicaci\u00f3n. Cuando la usamos bien, conseguimos aplicaciones mucho m\u00e1s f\u00e1ciles de: Pero cuando la usamos mal\u2026 los problemas pueden ser silenciosos [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18],"tags":[26,11],"class_list":["post-197","post","type-post","status-publish","format-standard","hentry","category-guia","tag-net","tag-c"],"_links":{"self":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/comments?post=197"}],"version-history":[{"count":0,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/197\/revisions"}],"wp:attachment":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/media?parent=197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/categories?post=197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/tags?post=197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}