[{"data":1,"prerenderedAt":1238},["ShallowReactive",2],{"blog-\u002Fblog\u002Fdotnet-di-basics":3},{"id":4,"title":5,"body":6,"description":1223,"difficulty":1224,"extension":1225,"framework":1226,"frameworkSlug":1227,"meta":1228,"navigation":84,"order":51,"path":1229,"qaPath":1230,"seo":1231,"stem":1232,"subtopic":1233,"topic":1234,"topicSlug":1235,"updated":1236,"__hash__":1237},"blog\u002Fblog\u002Fdotnet-di-basics.md","Dependency Injection in .NET Core",{"type":7,"value":8,"toc":1205},"minimark",[9,14,27,31,38,165,168,172,193,283,294,298,301,418,433,437,440,531,535,538,600,603,607,614,675,678,682,773,780,784,787,867,871,874,879,959,964,968,1065,1072,1076,1136,1140,1173,1176,1180,1201],[10,11,13],"h2",{"id":12},"why-di-knowledge-matters-in-net-interviews","Why DI knowledge matters in .NET interviews",[15,16,17,18,22,23,26],"p",{},"Dependency injection is the backbone of every ASP.NET Core application. Interviewers test it\nbecause the wrong approach — hardcoding ",[19,20,21],"code",{},"new",", injecting ",[19,24,25],{},"IServiceProvider",", or mixing\nlifetimes incorrectly — produces mysterious production bugs that are hard to reproduce and\nexpensive to fix. This article walks through how the built-in container works and which\npatterns to use (and avoid).",[10,28,30],{"id":29},"what-dependency-injection-actually-is","What dependency injection actually is",[15,32,33,37],{},[34,35,36],"strong",{},"Dependency injection"," means an object receives its dependencies from outside rather than\ncreating them itself. The container handles wiring.",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-csharp shiki shiki-themes github-light github-dark","\u002F\u002F WITHOUT DI — tight coupling; can't test or swap implementations:\npublic class OrderService\n{\n    private readonly EmailSender _emailSender = new EmailSender(); \u002F\u002F hardcoded\n}\n\n\u002F\u002F WITH DI — the container creates and injects the dependency:\npublic class OrderService\n{\n    private readonly IEmailSender _emailSender;\n\n    public OrderService(IEmailSender emailSender)\n    {\n        _emailSender = emailSender; \u002F\u002F injected; implementation is external\n    }\n}\n\n\u002F\u002F Wire-up:\nbuilder.Services.AddScoped\u003CIEmailSender, SmtpEmailSender>();\n\u002F\u002F Test: inject FakeEmailSender — OrderService doesn't change\n","csharp","",[19,46,47,55,61,67,73,79,86,92,97,102,108,113,119,125,131,137,142,147,153,159],{"__ignoreMap":44},[48,49,52],"span",{"class":50,"line":51},"line",1,[48,53,54],{},"\u002F\u002F WITHOUT DI — tight coupling; can't test or swap implementations:\n",[48,56,58],{"class":50,"line":57},2,[48,59,60],{},"public class OrderService\n",[48,62,64],{"class":50,"line":63},3,[48,65,66],{},"{\n",[48,68,70],{"class":50,"line":69},4,[48,71,72],{},"    private readonly EmailSender _emailSender = new EmailSender(); \u002F\u002F hardcoded\n",[48,74,76],{"class":50,"line":75},5,[48,77,78],{},"}\n",[48,80,82],{"class":50,"line":81},6,[48,83,85],{"emptyLinePlaceholder":84},true,"\n",[48,87,89],{"class":50,"line":88},7,[48,90,91],{},"\u002F\u002F WITH DI — the container creates and injects the dependency:\n",[48,93,95],{"class":50,"line":94},8,[48,96,60],{},[48,98,100],{"class":50,"line":99},9,[48,101,66],{},[48,103,105],{"class":50,"line":104},10,[48,106,107],{},"    private readonly IEmailSender _emailSender;\n",[48,109,111],{"class":50,"line":110},11,[48,112,85],{"emptyLinePlaceholder":84},[48,114,116],{"class":50,"line":115},12,[48,117,118],{},"    public OrderService(IEmailSender emailSender)\n",[48,120,122],{"class":50,"line":121},13,[48,123,124],{},"    {\n",[48,126,128],{"class":50,"line":127},14,[48,129,130],{},"        _emailSender = emailSender; \u002F\u002F injected; implementation is external\n",[48,132,134],{"class":50,"line":133},15,[48,135,136],{},"    }\n",[48,138,140],{"class":50,"line":139},16,[48,141,78],{},[48,143,145],{"class":50,"line":144},17,[48,146,85],{"emptyLinePlaceholder":84},[48,148,150],{"class":50,"line":149},18,[48,151,152],{},"\u002F\u002F Wire-up:\n",[48,154,156],{"class":50,"line":155},19,[48,157,158],{},"builder.Services.AddScoped\u003CIEmailSender, SmtpEmailSender>();\n",[48,160,162],{"class":50,"line":161},20,[48,163,164],{},"\u002F\u002F Test: inject FakeEmailSender — OrderService doesn't change\n",[15,166,167],{},"The four benefits are testability, loose coupling, lifetime management, and single responsibility.",[10,169,171],{"id":170},"iservicecollection-vs-iserviceprovider","IServiceCollection vs IServiceProvider",[15,173,174,177,178,181,182,185,186,188,189,192],{},[19,175,176],{},"IServiceCollection"," is the ",[34,179,180],{},"builder"," — a mutable list of service descriptors you populate\nduring app startup. ",[19,183,184],{},"builder.Build()"," converts it into an immutable ",[19,187,25],{}," — the\n",[34,190,191],{},"resolver"," that creates instances on demand.",[39,194,196],{"className":41,"code":195,"language":43,"meta":44,"style":44},"var builder = WebApplication.CreateBuilder(args);\n\n\u002F\u002F IServiceCollection — add entries during startup:\nbuilder.Services.AddScoped\u003CIOrderService, OrderService>();\nbuilder.Services.AddTransient\u003CIEmailSender, SmtpEmailSender>();\nbuilder.Services.AddSingleton\u003CICache, MemoryCache>();\n\n\u002F\u002F Factory delegate — for complex construction:\nbuilder.Services.AddSingleton\u003CIConnectionFactory>(sp =>\n{\n    var config = sp.GetRequiredService\u003CIConfiguration>();\n    return new SqlConnectionFactory(config[\"ConnectionStrings:Default\"]);\n});\n\nvar app = builder.Build(); \u002F\u002F IServiceCollection is frozen; IServiceProvider is ready\n\n\u002F\u002F IServiceProvider — resolve instances (mostly in infrastructure code):\nvar svc = app.Services.GetRequiredService\u003CIOrderService>(); \u002F\u002F throws if not registered\n",[19,197,198,203,207,212,217,222,227,231,236,241,245,250,255,260,264,269,273,278],{"__ignoreMap":44},[48,199,200],{"class":50,"line":51},[48,201,202],{},"var builder = WebApplication.CreateBuilder(args);\n",[48,204,205],{"class":50,"line":57},[48,206,85],{"emptyLinePlaceholder":84},[48,208,209],{"class":50,"line":63},[48,210,211],{},"\u002F\u002F IServiceCollection — add entries during startup:\n",[48,213,214],{"class":50,"line":69},[48,215,216],{},"builder.Services.AddScoped\u003CIOrderService, OrderService>();\n",[48,218,219],{"class":50,"line":75},[48,220,221],{},"builder.Services.AddTransient\u003CIEmailSender, SmtpEmailSender>();\n",[48,223,224],{"class":50,"line":81},[48,225,226],{},"builder.Services.AddSingleton\u003CICache, MemoryCache>();\n",[48,228,229],{"class":50,"line":88},[48,230,85],{"emptyLinePlaceholder":84},[48,232,233],{"class":50,"line":94},[48,234,235],{},"\u002F\u002F Factory delegate — for complex construction:\n",[48,237,238],{"class":50,"line":99},[48,239,240],{},"builder.Services.AddSingleton\u003CIConnectionFactory>(sp =>\n",[48,242,243],{"class":50,"line":104},[48,244,66],{},[48,246,247],{"class":50,"line":110},[48,248,249],{},"    var config = sp.GetRequiredService\u003CIConfiguration>();\n",[48,251,252],{"class":50,"line":115},[48,253,254],{},"    return new SqlConnectionFactory(config[\"ConnectionStrings:Default\"]);\n",[48,256,257],{"class":50,"line":121},[48,258,259],{},"});\n",[48,261,262],{"class":50,"line":127},[48,263,85],{"emptyLinePlaceholder":84},[48,265,266],{"class":50,"line":133},[48,267,268],{},"var app = builder.Build(); \u002F\u002F IServiceCollection is frozen; IServiceProvider is ready\n",[48,270,271],{"class":50,"line":139},[48,272,85],{"emptyLinePlaceholder":84},[48,274,275],{"class":50,"line":144},[48,276,277],{},"\u002F\u002F IServiceProvider — resolve instances (mostly in infrastructure code):\n",[48,279,280],{"class":50,"line":149},[48,281,282],{},"var svc = app.Services.GetRequiredService\u003CIOrderService>(); \u002F\u002F throws if not registered\n",[15,284,285,286,289,290,293],{},"Prefer ",[19,287,288],{},"GetRequiredService\u003CT>()"," over ",[19,291,292],{},"GetService\u003CT>()"," — the latter returns null silently.",[10,295,297],{"id":296},"constructor-injection-the-right-way","Constructor injection — the right way",[15,299,300],{},"Constructor injection is the default and preferred style. The container inspects the public\nconstructor, resolves each parameter type, and passes it in.",[39,302,304],{"className":41,"code":303,"language":43,"meta":44,"style":44},"public class CheckoutController : ControllerBase\n{\n    private readonly IOrderService _orders;\n    private readonly IPaymentGateway _payments;\n    private readonly ILogger\u003CCheckoutController> _logger;\n\n    \u002F\u002F All three resolved by the container — no manual wiring:\n    public CheckoutController(\n        IOrderService orders,\n        IPaymentGateway payments,\n        ILogger\u003CCheckoutController> logger)\n    {\n        _orders   = orders;\n        _payments = payments;\n        _logger   = logger;\n    }\n}\n\n\u002F\u002F In a unit test — no container required:\nvar sut = new CheckoutController(\n    new FakeOrderService(),\n    new FakePaymentGateway(),\n    NullLogger\u003CCheckoutController>.Instance);\n",[19,305,306,311,315,320,325,330,334,339,344,349,354,359,363,368,373,378,382,386,390,395,400,406,412],{"__ignoreMap":44},[48,307,308],{"class":50,"line":51},[48,309,310],{},"public class CheckoutController : ControllerBase\n",[48,312,313],{"class":50,"line":57},[48,314,66],{},[48,316,317],{"class":50,"line":63},[48,318,319],{},"    private readonly IOrderService _orders;\n",[48,321,322],{"class":50,"line":69},[48,323,324],{},"    private readonly IPaymentGateway _payments;\n",[48,326,327],{"class":50,"line":75},[48,328,329],{},"    private readonly ILogger\u003CCheckoutController> _logger;\n",[48,331,332],{"class":50,"line":81},[48,333,85],{"emptyLinePlaceholder":84},[48,335,336],{"class":50,"line":88},[48,337,338],{},"    \u002F\u002F All three resolved by the container — no manual wiring:\n",[48,340,341],{"class":50,"line":94},[48,342,343],{},"    public CheckoutController(\n",[48,345,346],{"class":50,"line":99},[48,347,348],{},"        IOrderService orders,\n",[48,350,351],{"class":50,"line":104},[48,352,353],{},"        IPaymentGateway payments,\n",[48,355,356],{"class":50,"line":110},[48,357,358],{},"        ILogger\u003CCheckoutController> logger)\n",[48,360,361],{"class":50,"line":115},[48,362,124],{},[48,364,365],{"class":50,"line":121},[48,366,367],{},"        _orders   = orders;\n",[48,369,370],{"class":50,"line":127},[48,371,372],{},"        _payments = payments;\n",[48,374,375],{"class":50,"line":133},[48,376,377],{},"        _logger   = logger;\n",[48,379,380],{"class":50,"line":139},[48,381,136],{},[48,383,384],{"class":50,"line":144},[48,385,78],{},[48,387,388],{"class":50,"line":149},[48,389,85],{"emptyLinePlaceholder":84},[48,391,392],{"class":50,"line":155},[48,393,394],{},"\u002F\u002F In a unit test — no container required:\n",[48,396,397],{"class":50,"line":161},[48,398,399],{},"var sut = new CheckoutController(\n",[48,401,403],{"class":50,"line":402},21,[48,404,405],{},"    new FakeOrderService(),\n",[48,407,409],{"class":50,"line":408},22,[48,410,411],{},"    new FakePaymentGateway(),\n",[48,413,415],{"class":50,"line":414},23,[48,416,417],{},"    NullLogger\u003CCheckoutController>.Instance);\n",[15,419,420,421,424,425,428,429,432],{},"Why it beats property injection and method injection: all dependencies are ",[34,422,423],{},"explicit"," in\nthe signature, ",[34,426,427],{},"required"," at construction time (no partial state), and ",[34,430,431],{},"testable"," without\na container.",[10,434,436],{"id":435},"registration-methods-add-tryadd-replace","Registration methods — Add, TryAdd, Replace",[15,438,439],{},"Choosing the right registration method matters especially in library code and test setup:",[39,441,443],{"className":41,"code":442,"language":43,"meta":44,"style":44},"\u002F\u002F Add* — always appends; multiple for the same type is valid:\nservices.AddSingleton\u003CICache, MemoryCache>();\nservices.AddSingleton\u003CICache, RedisCache>();\n\u002F\u002F GetService\u003CICache>()  → RedisCache (last wins)\n\u002F\u002F GetServices\u003CICache>() → [MemoryCache, RedisCache] (composite)\n\n\u002F\u002F TryAdd* — only registers if the type has no existing descriptor:\nservices.TryAddSingleton\u003CICache, MemoryCache>();\nservices.TryAddSingleton\u003CICache, RedisCache>(); \u002F\u002F ignored — MemoryCache already present\n\u002F\u002F Best for library\u002Fextension code: \"register a default, let apps override\"\n\n\u002F\u002F Replace — remove existing, then add:\nservices.AddSingleton\u003CICache, MemoryCache>();\nservices.Replace(ServiceDescriptor.Singleton\u003CICache, RedisCache>()); \u002F\u002F MemoryCache gone\n\n\u002F\u002F Test pattern — swap a real service with a fake:\nservices.RemoveAll\u003CIEmailSender>();\nservices.AddSingleton\u003CIEmailSender, FakeEmailSender>();\n",[19,444,445,450,455,460,465,470,474,479,484,489,494,498,503,507,512,516,521,526],{"__ignoreMap":44},[48,446,447],{"class":50,"line":51},[48,448,449],{},"\u002F\u002F Add* — always appends; multiple for the same type is valid:\n",[48,451,452],{"class":50,"line":57},[48,453,454],{},"services.AddSingleton\u003CICache, MemoryCache>();\n",[48,456,457],{"class":50,"line":63},[48,458,459],{},"services.AddSingleton\u003CICache, RedisCache>();\n",[48,461,462],{"class":50,"line":69},[48,463,464],{},"\u002F\u002F GetService\u003CICache>()  → RedisCache (last wins)\n",[48,466,467],{"class":50,"line":75},[48,468,469],{},"\u002F\u002F GetServices\u003CICache>() → [MemoryCache, RedisCache] (composite)\n",[48,471,472],{"class":50,"line":81},[48,473,85],{"emptyLinePlaceholder":84},[48,475,476],{"class":50,"line":88},[48,477,478],{},"\u002F\u002F TryAdd* — only registers if the type has no existing descriptor:\n",[48,480,481],{"class":50,"line":94},[48,482,483],{},"services.TryAddSingleton\u003CICache, MemoryCache>();\n",[48,485,486],{"class":50,"line":99},[48,487,488],{},"services.TryAddSingleton\u003CICache, RedisCache>(); \u002F\u002F ignored — MemoryCache already present\n",[48,490,491],{"class":50,"line":104},[48,492,493],{},"\u002F\u002F Best for library\u002Fextension code: \"register a default, let apps override\"\n",[48,495,496],{"class":50,"line":110},[48,497,85],{"emptyLinePlaceholder":84},[48,499,500],{"class":50,"line":115},[48,501,502],{},"\u002F\u002F Replace — remove existing, then add:\n",[48,504,505],{"class":50,"line":121},[48,506,454],{},[48,508,509],{"class":50,"line":127},[48,510,511],{},"services.Replace(ServiceDescriptor.Singleton\u003CICache, RedisCache>()); \u002F\u002F MemoryCache gone\n",[48,513,514],{"class":50,"line":133},[48,515,85],{"emptyLinePlaceholder":84},[48,517,518],{"class":50,"line":139},[48,519,520],{},"\u002F\u002F Test pattern — swap a real service with a fake:\n",[48,522,523],{"class":50,"line":144},[48,524,525],{},"services.RemoveAll\u003CIEmailSender>();\n",[48,527,528],{"class":50,"line":149},[48,529,530],{},"services.AddSingleton\u003CIEmailSender, FakeEmailSender>();\n",[10,532,534],{"id":533},"open-generic-registrations","Open generic registrations",[15,536,537],{},"One line covers all closed type combinations at resolve time:",[39,539,541],{"className":41,"code":540,"language":43,"meta":44,"style":44},"public interface IRepository\u003CT> where T : class { }\npublic class EfRepository\u003CT> : IRepository\u003CT> where T : class { }\n\n\u002F\u002F One open generic registration:\nbuilder.Services.AddScoped(typeof(IRepository\u003C>), typeof(EfRepository\u003C>));\n\n\u002F\u002F Resolves automatically for every entity type:\n\u002F\u002F IRepository\u003COrder>   → EfRepository\u003COrder>\n\u002F\u002F IRepository\u003CProduct> → EfRepository\u003CProduct>\n\n\u002F\u002F A closed registration overrides the open one:\nbuilder.Services.AddScoped\u003CIRepository\u003CAuditLog>, ReadOnlyAuditRepository>();\n",[19,542,543,548,553,557,562,567,571,576,581,586,590,595],{"__ignoreMap":44},[48,544,545],{"class":50,"line":51},[48,546,547],{},"public interface IRepository\u003CT> where T : class { }\n",[48,549,550],{"class":50,"line":57},[48,551,552],{},"public class EfRepository\u003CT> : IRepository\u003CT> where T : class { }\n",[48,554,555],{"class":50,"line":63},[48,556,85],{"emptyLinePlaceholder":84},[48,558,559],{"class":50,"line":69},[48,560,561],{},"\u002F\u002F One open generic registration:\n",[48,563,564],{"class":50,"line":75},[48,565,566],{},"builder.Services.AddScoped(typeof(IRepository\u003C>), typeof(EfRepository\u003C>));\n",[48,568,569],{"class":50,"line":81},[48,570,85],{"emptyLinePlaceholder":84},[48,572,573],{"class":50,"line":88},[48,574,575],{},"\u002F\u002F Resolves automatically for every entity type:\n",[48,577,578],{"class":50,"line":94},[48,579,580],{},"\u002F\u002F IRepository\u003COrder>   → EfRepository\u003COrder>\n",[48,582,583],{"class":50,"line":99},[48,584,585],{},"\u002F\u002F IRepository\u003CProduct> → EfRepository\u003CProduct>\n",[48,587,588],{"class":50,"line":104},[48,589,85],{"emptyLinePlaceholder":84},[48,591,592],{"class":50,"line":110},[48,593,594],{},"\u002F\u002F A closed registration overrides the open one:\n",[48,596,597],{"class":50,"line":115},[48,598,599],{},"builder.Services.AddScoped\u003CIRepository\u003CAuditLog>, ReadOnlyAuditRepository>();\n",[15,601,602],{},"Use open generics for repository, validator, and handler patterns where the same shape applies\nto many types.",[10,604,606],{"id":605},"keyed-services-net-8","Keyed services (.NET 8)",[15,608,609,610,613],{},"Before .NET 8, using multiple implementations of the same interface required ",[19,611,612],{},"Func\u003Cstring, T>","\nfactory workarounds. .NET 8 adds first-class keyed service support:",[39,615,617],{"className":41,"code":616,"language":43,"meta":44,"style":44},"\u002F\u002F Register with keys:\nbuilder.Services.AddKeyedSingleton\u003CICache, MemoryCache>(\"memory\");\nbuilder.Services.AddKeyedSingleton\u003CICache, RedisCache>(\"redis\");\n\n\u002F\u002F Inject by key:\npublic class ProductService\n{\n    public ProductService([FromKeyedServices(\"redis\")] ICache cache) { }\n}\n\n\u002F\u002F Or resolve programmatically:\nvar cache = sp.GetRequiredKeyedService\u003CICache>(\"memory\");\n",[19,618,619,624,629,634,638,643,648,652,657,661,665,670],{"__ignoreMap":44},[48,620,621],{"class":50,"line":51},[48,622,623],{},"\u002F\u002F Register with keys:\n",[48,625,626],{"class":50,"line":57},[48,627,628],{},"builder.Services.AddKeyedSingleton\u003CICache, MemoryCache>(\"memory\");\n",[48,630,631],{"class":50,"line":63},[48,632,633],{},"builder.Services.AddKeyedSingleton\u003CICache, RedisCache>(\"redis\");\n",[48,635,636],{"class":50,"line":69},[48,637,85],{"emptyLinePlaceholder":84},[48,639,640],{"class":50,"line":75},[48,641,642],{},"\u002F\u002F Inject by key:\n",[48,644,645],{"class":50,"line":81},[48,646,647],{},"public class ProductService\n",[48,649,650],{"class":50,"line":88},[48,651,66],{},[48,653,654],{"class":50,"line":94},[48,655,656],{},"    public ProductService([FromKeyedServices(\"redis\")] ICache cache) { }\n",[48,658,659],{"class":50,"line":99},[48,660,78],{},[48,662,663],{"class":50,"line":104},[48,664,85],{"emptyLinePlaceholder":84},[48,666,667],{"class":50,"line":110},[48,668,669],{},"\u002F\u002F Or resolve programmatically:\n",[48,671,672],{"class":50,"line":115},[48,673,674],{},"var cache = sp.GetRequiredKeyedService\u003CICache>(\"memory\");\n",[15,676,677],{},"Prefer enum keys over strings for type safety.",[10,679,681],{"id":680},"multiple-implementations-with-ienumerable","Multiple implementations with IEnumerable",[39,683,685],{"className":41,"code":684,"language":43,"meta":44,"style":44},"builder.Services.AddScoped\u003CIOrderValidator, StockValidator>();\nbuilder.Services.AddScoped\u003CIOrderValidator, PriceValidator>();\nbuilder.Services.AddScoped\u003CIOrderValidator, FraudValidator>();\n\npublic class OrderService\n{\n    \u002F\u002F All three are injected:\n    public OrderService(IEnumerable\u003CIOrderValidator> validators)\n    {\n        _validators = validators;\n    }\n\n    public async Task\u003Cbool> ValidateAsync(Order order)\n    {\n        foreach (var v in _validators)\n            if (!await v.ValidateAsync(order)) return false;\n        return true;\n    }\n}\n",[19,686,687,692,697,702,706,710,714,719,724,728,733,737,741,746,750,755,760,765,769],{"__ignoreMap":44},[48,688,689],{"class":50,"line":51},[48,690,691],{},"builder.Services.AddScoped\u003CIOrderValidator, StockValidator>();\n",[48,693,694],{"class":50,"line":57},[48,695,696],{},"builder.Services.AddScoped\u003CIOrderValidator, PriceValidator>();\n",[48,698,699],{"class":50,"line":63},[48,700,701],{},"builder.Services.AddScoped\u003CIOrderValidator, FraudValidator>();\n",[48,703,704],{"class":50,"line":69},[48,705,85],{"emptyLinePlaceholder":84},[48,707,708],{"class":50,"line":75},[48,709,60],{},[48,711,712],{"class":50,"line":81},[48,713,66],{},[48,715,716],{"class":50,"line":88},[48,717,718],{},"    \u002F\u002F All three are injected:\n",[48,720,721],{"class":50,"line":94},[48,722,723],{},"    public OrderService(IEnumerable\u003CIOrderValidator> validators)\n",[48,725,726],{"class":50,"line":99},[48,727,124],{},[48,729,730],{"class":50,"line":104},[48,731,732],{},"        _validators = validators;\n",[48,734,735],{"class":50,"line":110},[48,736,136],{},[48,738,739],{"class":50,"line":115},[48,740,85],{"emptyLinePlaceholder":84},[48,742,743],{"class":50,"line":121},[48,744,745],{},"    public async Task\u003Cbool> ValidateAsync(Order order)\n",[48,747,748],{"class":50,"line":127},[48,749,124],{},[48,751,752],{"class":50,"line":133},[48,753,754],{},"        foreach (var v in _validators)\n",[48,756,757],{"class":50,"line":139},[48,758,759],{},"            if (!await v.ValidateAsync(order)) return false;\n",[48,761,762],{"class":50,"line":144},[48,763,764],{},"        return true;\n",[48,766,767],{"class":50,"line":149},[48,768,136],{},[48,770,771],{"class":50,"line":155},[48,772,78],{},[15,774,775,776,779],{},"Use ",[19,777,778],{},"IEnumerable\u003CT>"," injection for composite, pipeline, and notification patterns.",[10,781,783],{"id":782},"extension-methods-keeping-programcs-clean","Extension methods — keeping Program.cs clean",[15,785,786],{},"Group related registrations into named extensions:",[39,788,790],{"className":41,"code":789,"language":43,"meta":44,"style":44},"public static class OrderingServiceExtensions\n{\n    public static IServiceCollection AddOrderingServices(\n        this IServiceCollection services, IConfiguration config)\n    {\n        services.AddScoped\u003CIOrderService, OrderService>();\n        services.AddScoped\u003CIOrderRepository, EfOrderRepository>();\n        services.AddScoped\u003CIOrderValidator, StockValidator>();\n        services.AddScoped\u003CIOrderValidator, PriceValidator>();\n        return services;\n    }\n}\n\n\u002F\u002F Program.cs — a readable feature index:\nbuilder.Services.AddOrderingServices(builder.Configuration);\nbuilder.Services.AddCatalogServices(builder.Configuration);\n",[19,791,792,797,801,806,811,815,820,825,830,835,840,844,848,852,857,862],{"__ignoreMap":44},[48,793,794],{"class":50,"line":51},[48,795,796],{},"public static class OrderingServiceExtensions\n",[48,798,799],{"class":50,"line":57},[48,800,66],{},[48,802,803],{"class":50,"line":63},[48,804,805],{},"    public static IServiceCollection AddOrderingServices(\n",[48,807,808],{"class":50,"line":69},[48,809,810],{},"        this IServiceCollection services, IConfiguration config)\n",[48,812,813],{"class":50,"line":75},[48,814,124],{},[48,816,817],{"class":50,"line":81},[48,818,819],{},"        services.AddScoped\u003CIOrderService, OrderService>();\n",[48,821,822],{"class":50,"line":88},[48,823,824],{},"        services.AddScoped\u003CIOrderRepository, EfOrderRepository>();\n",[48,826,827],{"class":50,"line":94},[48,828,829],{},"        services.AddScoped\u003CIOrderValidator, StockValidator>();\n",[48,831,832],{"class":50,"line":99},[48,833,834],{},"        services.AddScoped\u003CIOrderValidator, PriceValidator>();\n",[48,836,837],{"class":50,"line":104},[48,838,839],{},"        return services;\n",[48,841,842],{"class":50,"line":110},[48,843,136],{},[48,845,846],{"class":50,"line":115},[48,847,78],{},[48,849,850],{"class":50,"line":121},[48,851,85],{"emptyLinePlaceholder":84},[48,853,854],{"class":50,"line":127},[48,855,856],{},"\u002F\u002F Program.cs — a readable feature index:\n",[48,858,859],{"class":50,"line":133},[48,860,861],{},"builder.Services.AddOrderingServices(builder.Configuration);\n",[48,863,864],{"class":50,"line":139},[48,865,866],{},"builder.Services.AddCatalogServices(builder.Configuration);\n",[10,868,870],{"id":869},"the-three-di-anti-patterns","The three DI anti-patterns",[15,872,873],{},"These are the patterns interviewers specifically ask about because they produce subtle,\nhard-to-debug production failures.",[875,876,878],"h3",{"id":877},"_1-service-locator-hides-dependencies","1. Service Locator — hides dependencies",[39,880,882],{"className":41,"code":881,"language":43,"meta":44,"style":44},"\u002F\u002F Hidden deps — not testable without a real container:\npublic class OrderService\n{\n    private readonly IServiceProvider _sp;\n    public OrderService(IServiceProvider sp) => _sp = sp;\n\n    public void Process()\n    {\n        var repo = _sp.GetRequiredService\u003CIOrderRepository>(); \u002F\u002F hidden\n    }\n}\n\n\u002F\u002F Fix: inject the dependency directly:\npublic class OrderService\n{\n    public OrderService(IOrderRepository repo) { }\n}\n",[19,883,884,889,893,897,902,907,911,916,920,925,929,933,937,942,946,950,955],{"__ignoreMap":44},[48,885,886],{"class":50,"line":51},[48,887,888],{},"\u002F\u002F Hidden deps — not testable without a real container:\n",[48,890,891],{"class":50,"line":57},[48,892,60],{},[48,894,895],{"class":50,"line":63},[48,896,66],{},[48,898,899],{"class":50,"line":69},[48,900,901],{},"    private readonly IServiceProvider _sp;\n",[48,903,904],{"class":50,"line":75},[48,905,906],{},"    public OrderService(IServiceProvider sp) => _sp = sp;\n",[48,908,909],{"class":50,"line":81},[48,910,85],{"emptyLinePlaceholder":84},[48,912,913],{"class":50,"line":88},[48,914,915],{},"    public void Process()\n",[48,917,918],{"class":50,"line":94},[48,919,124],{},[48,921,922],{"class":50,"line":99},[48,923,924],{},"        var repo = _sp.GetRequiredService\u003CIOrderRepository>(); \u002F\u002F hidden\n",[48,926,927],{"class":50,"line":104},[48,928,136],{},[48,930,931],{"class":50,"line":110},[48,932,78],{},[48,934,935],{"class":50,"line":115},[48,936,85],{"emptyLinePlaceholder":84},[48,938,939],{"class":50,"line":121},[48,940,941],{},"\u002F\u002F Fix: inject the dependency directly:\n",[48,943,944],{"class":50,"line":127},[48,945,60],{},[48,947,948],{"class":50,"line":133},[48,949,66],{},[48,951,952],{"class":50,"line":139},[48,953,954],{},"    public OrderService(IOrderRepository repo) { }\n",[48,956,957],{"class":50,"line":144},[48,958,78],{},[15,960,961,963],{},[19,962,25],{}," belongs only in infrastructure code (factory delegates, hosted services,\nextension methods) — not in application classes.",[875,965,967],{"id":966},"_2-captive-dependency-wrong-lifetime-hierarchy","2. Captive dependency — wrong lifetime hierarchy",[39,969,971],{"className":41,"code":970,"language":43,"meta":44,"style":44},"\u002F\u002F Singleton holds a Scoped service — scoped service lives forever:\npublic class ProductCache  \u002F\u002F Singleton\n{\n    public ProductCache(AppDbContext db) { } \u002F\u002F AppDbContext is Scoped — BUG\n    \u002F\u002F db is shared across ALL requests — EF change tracker corrupts\n}\n\n\u002F\u002F Fix: inject IServiceScopeFactory:\npublic class ProductCache\n{\n    private readonly IServiceScopeFactory _factory;\n    public ProductCache(IServiceScopeFactory factory) => _factory = factory;\n\n    public async Task RefreshAsync()\n    {\n        using var scope = _factory.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n        _products = await db.Products.ToListAsync();\n    }\n}\n",[19,972,973,978,983,987,992,997,1001,1005,1010,1015,1019,1024,1029,1033,1038,1042,1047,1052,1057,1061],{"__ignoreMap":44},[48,974,975],{"class":50,"line":51},[48,976,977],{},"\u002F\u002F Singleton holds a Scoped service — scoped service lives forever:\n",[48,979,980],{"class":50,"line":57},[48,981,982],{},"public class ProductCache  \u002F\u002F Singleton\n",[48,984,985],{"class":50,"line":63},[48,986,66],{},[48,988,989],{"class":50,"line":69},[48,990,991],{},"    public ProductCache(AppDbContext db) { } \u002F\u002F AppDbContext is Scoped — BUG\n",[48,993,994],{"class":50,"line":75},[48,995,996],{},"    \u002F\u002F db is shared across ALL requests — EF change tracker corrupts\n",[48,998,999],{"class":50,"line":81},[48,1000,78],{},[48,1002,1003],{"class":50,"line":88},[48,1004,85],{"emptyLinePlaceholder":84},[48,1006,1007],{"class":50,"line":94},[48,1008,1009],{},"\u002F\u002F Fix: inject IServiceScopeFactory:\n",[48,1011,1012],{"class":50,"line":99},[48,1013,1014],{},"public class ProductCache\n",[48,1016,1017],{"class":50,"line":104},[48,1018,66],{},[48,1020,1021],{"class":50,"line":110},[48,1022,1023],{},"    private readonly IServiceScopeFactory _factory;\n",[48,1025,1026],{"class":50,"line":115},[48,1027,1028],{},"    public ProductCache(IServiceScopeFactory factory) => _factory = factory;\n",[48,1030,1031],{"class":50,"line":121},[48,1032,85],{"emptyLinePlaceholder":84},[48,1034,1035],{"class":50,"line":127},[48,1036,1037],{},"    public async Task RefreshAsync()\n",[48,1039,1040],{"class":50,"line":133},[48,1041,124],{},[48,1043,1044],{"class":50,"line":139},[48,1045,1046],{},"        using var scope = _factory.CreateScope();\n",[48,1048,1049],{"class":50,"line":144},[48,1050,1051],{},"        var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n",[48,1053,1054],{"class":50,"line":149},[48,1055,1056],{},"        _products = await db.Products.ToListAsync();\n",[48,1058,1059],{"class":50,"line":155},[48,1060,136],{},[48,1062,1063],{"class":50,"line":161},[48,1064,78],{},[15,1066,1067,1068,1071],{},"Enable ",[19,1069,1070],{},"ValidateScopes = true"," in development to catch this at startup.",[875,1073,1075],{"id":1074},"_3-bastard-injection-hidden-fallback","3. Bastard injection — hidden fallback",[39,1077,1079],{"className":41,"code":1078,"language":43,"meta":44,"style":44},"\u002F\u002F Misleading — looks injectable but silently uses a hardcoded default:\npublic class EmailService\n{\n    private readonly ILogger _logger;\n    public EmailService(ILogger logger = null)\n    {\n        _logger = logger ?? new ConsoleLogger(); \u002F\u002F hides the real dependency\n    }\n}\n\n\u002F\u002F Fix: require the dependency; provide NullLogger in tests:\npublic EmailService(ILogger\u003CEmailService> logger) => _logger = logger;\n",[19,1080,1081,1086,1091,1095,1100,1105,1109,1114,1118,1122,1126,1131],{"__ignoreMap":44},[48,1082,1083],{"class":50,"line":51},[48,1084,1085],{},"\u002F\u002F Misleading — looks injectable but silently uses a hardcoded default:\n",[48,1087,1088],{"class":50,"line":57},[48,1089,1090],{},"public class EmailService\n",[48,1092,1093],{"class":50,"line":63},[48,1094,66],{},[48,1096,1097],{"class":50,"line":69},[48,1098,1099],{},"    private readonly ILogger _logger;\n",[48,1101,1102],{"class":50,"line":75},[48,1103,1104],{},"    public EmailService(ILogger logger = null)\n",[48,1106,1107],{"class":50,"line":81},[48,1108,124],{},[48,1110,1111],{"class":50,"line":88},[48,1112,1113],{},"        _logger = logger ?? new ConsoleLogger(); \u002F\u002F hides the real dependency\n",[48,1115,1116],{"class":50,"line":94},[48,1117,136],{},[48,1119,1120],{"class":50,"line":99},[48,1121,78],{},[48,1123,1124],{"class":50,"line":104},[48,1125,85],{"emptyLinePlaceholder":84},[48,1127,1128],{"class":50,"line":110},[48,1129,1130],{},"\u002F\u002F Fix: require the dependency; provide NullLogger in tests:\n",[48,1132,1133],{"class":50,"line":115},[48,1134,1135],{},"public EmailService(ILogger\u003CEmailService> logger) => _logger = logger;\n",[10,1137,1139],{"id":1138},"validateonbuild-and-validatescopes","ValidateOnBuild and ValidateScopes",[39,1141,1143],{"className":41,"code":1142,"language":43,"meta":44,"style":44},"builder.Host.UseDefaultServiceProvider((ctx, options) =>\n{\n    bool isDev = ctx.HostingEnvironment.IsDevelopment();\n    options.ValidateOnBuild = isDev; \u002F\u002F catch missing registrations at startup\n    options.ValidateScopes  = isDev; \u002F\u002F catch captive dependencies at startup\n});\n",[19,1144,1145,1150,1154,1159,1164,1169],{"__ignoreMap":44},[48,1146,1147],{"class":50,"line":51},[48,1148,1149],{},"builder.Host.UseDefaultServiceProvider((ctx, options) =>\n",[48,1151,1152],{"class":50,"line":57},[48,1153,66],{},[48,1155,1156],{"class":50,"line":63},[48,1157,1158],{},"    bool isDev = ctx.HostingEnvironment.IsDevelopment();\n",[48,1160,1161],{"class":50,"line":69},[48,1162,1163],{},"    options.ValidateOnBuild = isDev; \u002F\u002F catch missing registrations at startup\n",[48,1165,1166],{"class":50,"line":75},[48,1167,1168],{},"    options.ValidateScopes  = isDev; \u002F\u002F catch captive dependencies at startup\n",[48,1170,1171],{"class":50,"line":81},[48,1172,259],{},[15,1174,1175],{},"Without these, misconfigured containers fail silently on the first production request that\nexercises the broken dependency path — usually at 3 AM.",[10,1177,1179],{"id":1178},"recap","Recap",[15,1181,1182,1183,1185,1186,1188,1189,1192,1193,1196,1197,1200],{},"The .NET DI container wires your application at startup via ",[19,1184,176],{}," and resolves\nservices at runtime via ",[19,1187,25],{},". Constructor injection is the default and preferred\nstyle — explicit, required, testable. Register interfaces against implementations; use\n",[19,1190,1191],{},"TryAdd*"," in library code. Use open generics for uniform patterns across many types. Use keyed\nservices (.NET 8+) for named variants. Avoid Service Locator, captive dependencies, and bastard\ninjection. Enable ",[19,1194,1195],{},"ValidateOnBuild"," and ",[19,1198,1199],{},"ValidateScopes"," in development to catch misconfiguration\nat startup, not under production load.",[1202,1203,1204],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":44,"searchDepth":57,"depth":57,"links":1206},[1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1221,1222],{"id":12,"depth":57,"text":13},{"id":29,"depth":57,"text":30},{"id":170,"depth":57,"text":171},{"id":296,"depth":57,"text":297},{"id":435,"depth":57,"text":436},{"id":533,"depth":57,"text":534},{"id":605,"depth":57,"text":606},{"id":680,"depth":57,"text":681},{"id":782,"depth":57,"text":783},{"id":869,"depth":57,"text":870,"children":1217},[1218,1219,1220],{"id":877,"depth":63,"text":878},{"id":966,"depth":63,"text":967},{"id":1074,"depth":63,"text":1075},{"id":1138,"depth":57,"text":1139},{"id":1178,"depth":57,"text":1179},"The .NET Core DI container from the ground up — IServiceCollection, constructor injection, open generic registrations, keyed services, and the three anti-patterns that interviewers routinely probe.","medium","md",".NET Core","dotnet",{},"\u002Fblog\u002Fdotnet-di-basics","\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics",{"title":5,"description":1223},"blog\u002Fdotnet-di-basics","DI Basics","Dependency Injection","dependency-injection","2026-06-23","kBtDWIQ8jxym-sE1OT0ryXwepGygZ8fjYWOUw0dV310",1782244083923]