[{"data":1,"prerenderedAt":107},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics":3},{"page":4,"siblings":95,"blog":104},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":90,"topic":91,"topicSlug":92,"updated":93,"__hash__":94},"qa\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics.md","Di Basics",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,1,"\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,81],{"id":24,"difficulty":25,"q":26,"a":27},"di-what-is","easy","What is dependency injection and why does ASP.NET Core use it by default?","**Dependency injection (DI)** is a design pattern where an object's dependencies are\n*provided* (injected) rather than created by the object itself. ASP.NET Core ships with\na built-in IoC container that manages object creation and lifetime.\n\n```csharp\n\u002F\u002F WITHOUT DI — tight coupling; impossible to swap or test in isolation:\npublic class OrderService\n{\n    \u002F\u002F hardcoded — can't change implementation or mock in tests\n    private readonly EmailSender _emailSender = new EmailSender();\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) \u002F\u002F injected by the container\n    {\n        _emailSender = emailSender;\n    }\n}\n\n\u002F\u002F Wire-up in Program.cs:\nbuilder.Services.AddScoped\u003CIEmailSender, SmtpEmailSender>();\n\u002F\u002F Now the container resolves SmtpEmailSender whenever IEmailSender is needed.\n\u002F\u002F In tests, inject FakeEmailSender instead — no code change in OrderService.\n```\n\nBenefits:\n- **Testability** — swap real implementations with fakes\u002Fmocks without touching consumers.\n- **Loose coupling** — classes depend on abstractions, not concrete types.\n- **Lifetime management** — the container controls creation and disposal.\n- **Single responsibility** — classes focus on their job; the container owns wiring.\n\n**Rule of thumb:** If a class creates its dependencies with `new`, it can't be tested\nin isolation and can't be configured from outside. Inject dependencies instead.\n",{"id":29,"difficulty":25,"q":30,"a":31},"iservicecollection","What is IServiceCollection and how do you register services with it?","**`IServiceCollection`** is the container builder — a mutable list of `ServiceDescriptor`\nentries that each describe a service type, implementation, and lifetime. After\n`builder.Build()` is called, it produces an immutable **`IServiceProvider`** (the resolver).\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\n\u002F\u002F Interface → concrete type (preferred — keeps consumers decoupled):\nbuilder.Services.AddScoped\u003CIOrderService, OrderService>();\nbuilder.Services.AddTransient\u003CIEmailSender, SmtpEmailSender>();\n\n\u002F\u002F Concrete type only (useful for internal\u002Finfrastructure classes):\nbuilder.Services.AddSingleton\u003CGreeterService>();\n\n\u002F\u002F Factory delegate (for complex construction logic):\nbuilder.Services.AddSingleton\u003CIConnectionFactory>(sp =>\n{\n    var config = sp.GetRequiredService\u003CIConfiguration>();\n    return new SqlConnectionFactory(config[\"ConnectionStrings:Default\"]);\n});\n\n\u002F\u002F Pre-built instance (rarely needed — loses lifetime management):\nbuilder.Services.AddSingleton\u003CICache>(new RedisCache(\"localhost:6379\"));\n\nvar app = builder.Build(); \u002F\u002F freezes the container — no more registrations after this\n```\n\nResolving services:\n```csharp\n\u002F\u002F GetRequiredService\u003CT> — throws InvalidOperationException if not registered:\nvar svc = app.Services.GetRequiredService\u003CIOrderService>(); \u002F\u002F prefer this\n\n\u002F\u002F GetService\u003CT> — returns null if not registered (easy to miss):\nvar svc = app.Services.GetService\u003CIOrderService>(); \u002F\u002F avoid in application code\n```\n\n**Rule of thumb:** Register against interfaces, not concrete types. This keeps\nconsumers decoupled and lets you swap implementations without changing call sites.\n",{"id":33,"difficulty":25,"q":34,"a":35},"constructor-injection","How does constructor injection work in ASP.NET Core and why is it the preferred style?","**Constructor injection** is the default DI style. The container inspects the class's\npublic constructor, resolves each parameter type from the registered services, and passes\nthem in. If a parameter type isn't registered, the container throws at resolve time.\n\n```csharp\npublic 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 and injected 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    [HttpPost(\"checkout\")]\n    public async Task\u003CIActionResult> Checkout([FromBody] CartDto cart)\n    {\n        _logger.LogInformation(\"Checkout: {Count} items\", cart.Items.Count);\n        var order = await _orders.CreateAsync(cart);\n        await _payments.ChargeAsync(order);\n        return Ok(order);\n    }\n}\n\n\u002F\u002F In a test — inject fakes directly; no container needed:\nvar sut = new CheckoutController(\n    new FakeOrderService(),\n    new FakePaymentGateway(),\n    NullLogger\u003CCheckoutController>.Instance);\n```\n\nWhy constructor injection beats alternatives:\n- **Explicit** — all dependencies visible in the constructor signature.\n- **Required** — object can't be built without its dependencies; no partial state.\n- **Testable** — pass fakes directly to the constructor in tests.\n- **Container-free** — the class doesn't reference `IServiceProvider` at all.\n\n**Rule of thumb:** Inject through the constructor. If the parameter list exceeds 4–5\nitems, that's a signal the class has too many responsibilities.\n",{"id":37,"difficulty":14,"q":38,"a":39},"iserviceprovider-resolve","What is IServiceProvider and when is it appropriate to use it directly?","**`IServiceProvider`** is the read-only runtime resolver that creates and returns\nregistered service instances. Injecting it into application classes is the\n**Service Locator anti-pattern** — it hides dependencies and breaks testability.\n\n```csharp\n\u002F\u002F Anti-pattern — dependencies hidden inside method bodies:\npublic class ReportService\n{\n    private readonly IServiceProvider _sp;\n    public ReportService(IServiceProvider sp) => _sp = sp;\n\n    public void Generate()\n    {\n        \u002F\u002F Callers can't see what this class actually needs:\n        var repo   = _sp.GetRequiredService\u003CIReportRepository>();\n        var mailer = _sp.GetRequiredService\u003CIMailer>();\n    }\n}\n\n\u002F\u002F Constructor injection — all dependencies are explicit:\npublic class ReportService\n{\n    public ReportService(IReportRepository repo, IMailer mailer) { }\n}\n\n\u002F\u002F Legitimate use 1: factory delegate inside registration\nbuilder.Services.AddSingleton\u003CICache>(sp =>\n{\n    var cfg = sp.GetRequiredService\u003CIConfiguration>();\n    return new RedisCache(cfg[\"Redis:Host\"]);\n});\n\n\u002F\u002F Legitimate use 2: background service needs scoped service\npublic class DataSyncJob : BackgroundService\n{\n    private readonly IServiceScopeFactory _scopeFactory;\n\n    public DataSyncJob(IServiceScopeFactory scopeFactory)\n        => _scopeFactory = scopeFactory;\n\n    protected override async Task ExecuteAsync(CancellationToken ct)\n    {\n        using var scope = _scopeFactory.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n        \u002F\u002F db is scoped — safe here because we created an explicit scope\n    }\n}\n```\n\n**Rule of thumb:** `IServiceProvider` belongs in infrastructure code only — factory\ndelegates, hosted services, extension methods. In application classes, always use\nconstructor injection.\n",{"id":41,"difficulty":14,"q":42,"a":43},"add-vs-tryadd","What is the difference between Add*, TryAdd*, and Replace* registration methods?","The three registration families differ in behavior when a service type is already\nregistered — a critical distinction for library authors and test setup.\n\n```csharp\n\u002F\u002F Add* — always appends a new descriptor (multiple for same type is valid):\nservices.AddSingleton\u003CICache, MemoryCache>();\nservices.AddSingleton\u003CICache, RedisCache>();\n\u002F\u002F Both registered. GetService\u003CICache>() → RedisCache (last wins).\n\u002F\u002F GetServices\u003CICache>() → [MemoryCache, RedisCache] (composite pattern).\n\n\u002F\u002F TryAdd* — adds only if NO descriptor for the type exists yet:\nservices.TryAddSingleton\u003CICache, MemoryCache>();\nservices.TryAddSingleton\u003CICache, RedisCache>(); \u002F\u002F ignored — MemoryCache already present\n\u002F\u002F Best for library\u002Fframework code: register a default that the app can override.\n\n\u002F\u002F Replace — removes the existing descriptor, then adds the new one:\nservices.AddSingleton\u003CICache, MemoryCache>();\nservices.Replace(ServiceDescriptor.Singleton\u003CICache, RedisCache>());\n\u002F\u002F Only RedisCache is registered — MemoryCache is gone.\n\n\u002F\u002F RemoveAll — removes all descriptors for a type:\nservices.RemoveAll\u003CICache>();\n\n\u002F\u002F Test setup pattern — swap real service with a fake:\nservices.RemoveAll\u003CIEmailSender>();\nservices.AddSingleton\u003CIEmailSender, FakeEmailSender>();\n```\n\n**Rule of thumb:** Use `Add*` in application code where you control all registrations.\nUse `TryAdd*` in library\u002Fextension code so apps can override defaults. Use `Replace`\nin test fixtures to swap implementations cleanly.\n",{"id":45,"difficulty":14,"q":46,"a":47},"open-generics","How do you register open generic services in the .NET DI container?","**Open generic registration** maps a generic interface to a generic implementation\nwith a single line — the container closes the type arguments on demand at resolve time.\n\n```csharp\n\u002F\u002F Generic repository pattern:\npublic interface IRepository\u003CT> where T : class { }\npublic class EfRepository\u003CT> : IRepository\u003CT> where T : class\n{\n    private readonly AppDbContext _db;\n    public EfRepository(AppDbContext db) => _db = db;\n}\n\n\u002F\u002F One registration covers every entity type:\nbuilder.Services.AddScoped(typeof(IRepository\u003C>), typeof(EfRepository\u003C>));\n\n\u002F\u002F Resolved automatically for any closed type:\n\u002F\u002F IRepository\u003COrder>   → new EfRepository\u003COrder>(db)\n\u002F\u002F IRepository\u003CProduct> → new EfRepository\u003CProduct>(db)\n\u002F\u002F IRepository\u003CUser>    → new EfRepository\u003CUser>(db)\n\n\u002F\u002F A closed registration overrides the open one for a specific type:\nbuilder.Services.AddScoped\u003CIRepository\u003CAuditLog>, ReadOnlyAuditRepository>();\n\u002F\u002F IRepository\u003CAuditLog> → ReadOnlyAuditRepository (not EfRepository\u003CAuditLog>)\n\n\u002F\u002F Consumer — unchanged regardless of how many entity types exist:\npublic class OrderService\n{\n    public OrderService(IRepository\u003COrder> orders) { } \u002F\u002F resolved from open generic\n}\n```\n\nWorks with all three lifetimes (Singleton, Scoped, Transient).\n\n**Rule of thumb:** Use open generic registration when a pattern (repository, validator,\nhandler) applies uniformly across many types. One line replaces dozens of individual\nclosed-type registrations.\n",{"id":49,"difficulty":14,"q":50,"a":51},"keyed-services","What are keyed services in .NET 8 and when should you use them?","**Keyed services** (.NET 8+) allow registering multiple implementations of the same\ninterface under distinct keys and resolving them by key — the official replacement for\nolder `Func\u003Cstring, T>` factory workarounds.\n\n```csharp\n\u002F\u002F Register multiple implementations under string keys:\nbuilder.Services.AddKeyedSingleton\u003CICache, MemoryCache>(\"memory\");\nbuilder.Services.AddKeyedSingleton\u003CICache, RedisCache>(\"redis\");\nbuilder.Services.AddKeyedSingleton\u003CICache, NullCache>(\"null\");\n\n\u002F\u002F Constructor injection — [FromKeyedServices] attribute specifies the key:\npublic class ProductService\n{\n    public ProductService([FromKeyedServices(\"redis\")] ICache cache)\n    {\n        _cache = cache; \u002F\u002F RedisCache\n    }\n}\n\n\u002F\u002F Programmatic resolution:\nvar cache = sp.GetRequiredKeyedService\u003CICache>(\"memory\");\n\n\u002F\u002F Keys can be enums, not just strings:\npublic enum CacheType { Memory, Redis }\n\nbuilder.Services.AddKeyedSingleton\u003CICache, MemoryCache>(CacheType.Memory);\nbuilder.Services.AddKeyedSingleton\u003CICache, RedisCache>(CacheType.Redis);\n\n\u002F\u002F Pre-.NET 8 workaround (for reference):\nbuilder.Services.AddSingleton\u003CFunc\u003Cstring, ICache>>(sp => key => key switch\n{\n    \"redis\"  => sp.GetRequiredService\u003CRedisCache>(),\n    \"memory\" => sp.GetRequiredService\u003CMemoryCache>(),\n    _        => throw new ArgumentException($\"Unknown cache: {key}\")\n});\n```\n\n**Rule of thumb:** Use keyed services (.NET 8+) when you need multiple named\nimplementations of the same interface. Prefer enum keys over strings for type safety.\n",{"id":53,"difficulty":14,"q":54,"a":55},"multiple-implementations","How do you resolve all registered implementations of an interface?","When multiple implementations of the same interface are registered, inject\n**`IEnumerable\u003CT>`** to receive all of them — the core of composite, pipeline, and\nnotification patterns.\n\n```csharp\n\u002F\u002F Register three validators for the same interface:\nbuilder.Services.AddScoped\u003CIOrderValidator, StockValidator>();\nbuilder.Services.AddScoped\u003CIOrderValidator, PriceValidator>();\nbuilder.Services.AddScoped\u003CIOrderValidator, FraudValidator>();\n\n\u002F\u002F Inject IEnumerable\u003CT> to consume all of them:\npublic class OrderService\n{\n    private readonly IEnumerable\u003CIOrderValidator> _validators;\n\n    public OrderService(IEnumerable\u003CIOrderValidator> validators)\n        => _validators = validators;\n\n    public async Task\u003CValidationResult> ValidateAsync(Order order)\n    {\n        foreach (var validator in _validators)\n        {\n            var result = await validator.ValidateAsync(order);\n            if (!result.IsValid) return result; \u002F\u002F fail fast on first failure\n        }\n        return ValidationResult.Success;\n    }\n}\n\n\u002F\u002F Programmatic resolution:\n\u002F\u002F GetService\u003CIOrderValidator>()   → FraudValidator   (last registered wins)\n\u002F\u002F GetServices\u003CIOrderValidator>()  → all three in registration order\nvar all = sp.GetServices\u003CIOrderValidator>();\n```\n\n**Rule of thumb:** Use `IEnumerable\u003CT>` injection for composite and pipeline patterns\nwhere all registered implementations should participate. For a single-winner scenario,\nthe last registered implementation wins with `GetService\u003CT>`.\n",{"id":57,"difficulty":14,"q":58,"a":59},"validate-on-build","What are ValidateOnBuild and ValidateScopes and why should you enable them in development?","**`ValidateOnBuild`** verifies the entire service graph at startup and throws if any\nregistered service has an unresolvable dependency. **`ValidateScopes`** detects captive\ndependency bugs (a scoped service captured by a singleton).\n\n```csharp\n\u002F\u002F Enable in development via UseDefaultServiceProvider:\nbuilder.Host.UseDefaultServiceProvider((ctx, options) =>\n{\n    bool isDev = ctx.HostingEnvironment.IsDevelopment();\n    options.ValidateOnBuild = isDev;  \u002F\u002F fail at startup, not first HTTP request\n    options.ValidateScopes  = isDev;  \u002F\u002F catch lifetime mismatches at startup\n});\n\n\u002F\u002F Example: ValidateOnBuild catches this missing registration immediately:\nbuilder.Services.AddScoped\u003CIOrderService, OrderService>();\n\u002F\u002F If OrderService's constructor needs IPaymentGateway but it's not registered,\n\u002F\u002F app.Build() throws — not the first production request that hits the endpoint.\n\n\u002F\u002F Example: ValidateScopes catches captive dependencies:\nbuilder.Services.AddSingleton\u003CReportCache>();\nbuilder.Services.AddScoped\u003CAppDbContext>();\n\npublic class ReportCache\n{\n    \u002F\u002F Captive dependency — AppDbContext (scoped) held by singleton:\n    public ReportCache(AppDbContext db) { }\n}\n\u002F\u002F With ValidateScopes = true, app.Build() throws InvalidOperationException.\n```\n\nThese options add startup overhead; only enable them in development or test environments.\n\n**Rule of thumb:** Always enable `ValidateOnBuild` and `ValidateScopes` in development.\nMisconfigured DI graphs that slip to production cause intermittent, hard-to-reproduce bugs.\n",{"id":61,"difficulty":25,"q":62,"a":63},"di-in-minimal-api","How does dependency injection work in minimal API endpoint handlers?","Minimal APIs resolve registered services **by parameter type** automatically — no\n`[FromServices]` attribute needed in most cases. The framework distinguishes services\nfrom route parameters and model bindings by type.\n\n```csharp\nbuilder.Services.AddScoped\u003CIProductService, ProductService>();\nbuilder.Services.AddSingleton\u003CICache, MemoryCache>();\n\nvar app = builder.Build();\n\n\u002F\u002F Services are injected by type; route params come from the URL:\napp.MapGet(\"\u002Fproducts\u002F{id:int}\", async (\n    int id,                              \u002F\u002F from route — not a service\n    IProductService products,            \u002F\u002F from DI\n    ICache cache,                        \u002F\u002F from DI\n    ILogger\u003CProgram> logger) =>          \u002F\u002F from DI (built-in)\n{\n    logger.LogInformation(\"Fetching product {Id}\", id);\n    var product = await products.GetByIdAsync(id);\n    return product is null ? Results.NotFound() : Results.Ok(product);\n});\n\n\u002F\u002F [FromServices] is explicit — use when the type is ambiguous:\napp.MapPost(\"\u002Forders\", async (\n    [FromBody] CreateOrderDto dto,\n    [FromServices] IOrderService orders) =>\n{\n    var order = await orders.CreateAsync(dto);\n    return Results.Created($\"\u002Forders\u002F{order.Id}\", order);\n});\n```\n\n**Rule of thumb:** In minimal APIs, registered services are injected automatically by\nparameter type. Use `[FromServices]` only when there's ambiguity or for explicitness.\n",{"id":65,"difficulty":25,"q":66,"a":67},"extension-methods","How do extension methods help organize service registrations?","**Extension methods on `IServiceCollection`** group related registrations into a named,\ncomposable unit — the same pattern Microsoft uses for `AddDbContext`, `AddAuthentication`,\nand `AddMvc`.\n\n```csharp\n\u002F\u002F Group all ordering-related registrations:\npublic static class OrderingServiceExtensions\n{\n    public static IServiceCollection AddOrderingServices(\n        this IServiceCollection services, IConfiguration configuration)\n    {\n        services.AddScoped\u003CIOrderService, OrderService>();\n        services.AddScoped\u003CIOrderRepository, EfOrderRepository>();\n        services.AddScoped\u003CIOrderValidator, StockValidator>();\n        services.AddScoped\u003CIOrderValidator, PriceValidator>();\n        services.AddSingleton\u003CIOrderEventPublisher>(sp =>\n        {\n            var cfg = configuration.GetSection(\"EventBus\");\n            return new RabbitMqPublisher(cfg[\"Host\"], cfg[\"Exchange\"]);\n        });\n\n        return services; \u002F\u002F enables fluent chaining\n    }\n}\n\n\u002F\u002F Program.cs stays clean — reads as a feature list, not a type list:\nbuilder.Services.AddOrderingServices(builder.Configuration);\nbuilder.Services.AddCatalogServices(builder.Configuration);\nbuilder.Services.AddAuthServices(builder.Configuration);\nbuilder.Services.AddInfrastructureServices(builder.Configuration);\n```\n\n**Rule of thumb:** Once a domain area has 3+ registrations, extract them into an\n`Add\u003CFeature>Services` extension method. `Program.cs` should read like a table of\ncontents, not a registry dump.\n",{"id":69,"difficulty":14,"q":70,"a":71},"di-anti-patterns","What are the most common DI anti-patterns in ASP.NET Core?","Three anti-patterns cause the most production bugs in ASP.NET Core DI:\n\n```csharp\n\u002F\u002F 1. Service Locator — hides dependencies, breaks testability:\npublic class OrderService\n{\n    private readonly IServiceProvider _sp;\n    public OrderService(IServiceProvider sp) => _sp = sp;\n\n    public void Process()\n    {\n        \u002F\u002F Callers can't see what this class needs:\n        var repo = _sp.GetRequiredService\u003CIOrderRepository>();\n    }\n}\n\u002F\u002F Fix: inject IOrderRepository directly in the constructor.\n\n\u002F\u002F 2. Captive dependency — scoped service held by a singleton:\npublic class ReportCache  \u002F\u002F registered as Singleton\n{\n    public ReportCache(AppDbContext db) { } \u002F\u002F AppDbContext is Scoped\n    \u002F\u002F db outlives the request — shared state across requests → data corruption\n}\n\u002F\u002F Fix: inject IServiceScopeFactory; create a new scope per operation:\npublic class ReportCache\n{\n    private readonly IServiceScopeFactory _factory;\n    public ReportCache(IServiceScopeFactory factory) => _factory = factory;\n\n    public void Refresh()\n    {\n        using var scope = _factory.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n        \u002F\u002F db is properly scoped here\n    }\n}\n\n\u002F\u002F 3. Bastard injection — hidden fallback dependency in the constructor:\npublic class EmailService\n{\n    private readonly ILogger _logger;\n    public EmailService(ILogger logger = null)\n    {\n        _logger = logger ?? new ConsoleLogger(); \u002F\u002F misleading in tests\n    }\n}\n\u002F\u002F Fix: always require the dependency; use NullLogger.Instance in tests:\npublic EmailService(ILogger\u003CEmailService> logger) => _logger = logger;\n```\n\n**Rule of thumb:** If a class reaches into `IServiceProvider`, it has a Service Locator.\nIf a singleton holds a scoped service in its constructor, it's a captive dependency.\nBoth produce mysterious, request-intermittent bugs — catch them with `ValidateScopes`.\n",{"id":73,"difficulty":14,"q":74,"a":75},"property-method-injection","Does ASP.NET Core's built-in DI container support property or method injection?","The built-in container supports **constructor injection only**. Property and method\ninjection require a third-party container (Autofac, Castle Windsor) or a manual workaround.\nThis is by design — constructor injection makes dependencies explicit and verifiable.\n\n```csharp\n\u002F\u002F Built-in container: constructor injection only — this is the right approach:\npublic class ReportService\n{\n    private readonly IReportRepository _repo;\n    private readonly ILogger\u003CReportService> _logger;\n\n    public ReportService(IReportRepository repo, ILogger\u003CReportService> logger)\n    {\n        _repo   = repo;\n        _logger = logger;\n    }\n}\n\n\u002F\u002F Property injection — NOT supported by the built-in container:\npublic class ReportService\n{\n    \u002F\u002F The built-in container will NOT set this automatically:\n    public IReportRepository Repo { get; set; } = null!; \u002F\u002F Bad: hidden dependency\n}\n\n\u002F\u002F Workaround if property injection is genuinely needed — factory delegate:\nbuilder.Services.AddScoped\u003CReportService>(sp =>\n{\n    var svc = new ReportService();          \u002F\u002F parameterless ctor\n    svc.Repo = sp.GetRequiredService\u003CIReportRepository>(); \u002F\u002F manual wiring\n    return svc;\n});\n\u002F\u002F Note: still visible at the registration site — but hides deps from consumers.\n\n\u002F\u002F Third-party container (Autofac) — supports property injection natively:\nbuilder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());\n\u002F\u002F Then use ContainerBuilder.RegisterType\u003CT>().PropertiesAutowired() in Autofac config.\n```\n\n**Rule of thumb:** Stick with constructor injection and the built-in container for 95%\nof projects. Only reach for property injection (and a third-party container) when\nintegrating with frameworks that require a parameterless constructor.\n",{"id":77,"difficulty":78,"q":79,"a":80},"decorator-pattern-di","hard","How do you implement the decorator pattern with the built-in DI container?","The built-in container doesn't have first-class decorator support, but you can wire\ndecorators using **factory delegates** that resolve the inner service and wrap it.\nScrutor (a NuGet package) adds a `.Decorate\u003CTInterface, TDecorator>()` extension for\ncleaner syntax.\n\n```csharp\npublic interface IOrderRepository\n{\n    Task\u003COrder?> GetByIdAsync(int id);\n    Task SaveAsync(Order order);\n}\n\npublic class EfOrderRepository : IOrderRepository { \u002F* EF Core implementation *\u002F }\n\n\u002F\u002F Decorator: adds caching around the real repository:\npublic class CachedOrderRepository : IOrderRepository\n{\n    private readonly IOrderRepository _inner; \u002F\u002F wraps the real implementation\n    private readonly IMemoryCache _cache;\n\n    public CachedOrderRepository(IOrderRepository inner, IMemoryCache cache)\n    {\n        _inner = inner;\n        _cache = cache;\n    }\n\n    public async Task\u003COrder?> GetByIdAsync(int id)\n    {\n        return await _cache.GetOrCreateAsync($\"order:{id}\", async entry =>\n        {\n            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n            return await _inner.GetByIdAsync(id); \u002F\u002F delegates to real repo\n        });\n    }\n\n    public Task SaveAsync(Order order)\n    {\n        _cache.Remove($\"order:{order.Id}\"); \u002F\u002F invalidate on write\n        return _inner.SaveAsync(order);\n    }\n}\n\n\u002F\u002F Manual wiring with factory delegate:\nbuilder.Services.AddScoped\u003CEfOrderRepository>();  \u002F\u002F register concrete inner\nbuilder.Services.AddScoped\u003CIOrderRepository>(sp =>\n    new CachedOrderRepository(\n        sp.GetRequiredService\u003CEfOrderRepository>(), \u002F\u002F inner resolved by concrete type\n        sp.GetRequiredService\u003CIMemoryCache>()));\n\n\u002F\u002F Cleaner with Scrutor (Microsoft.Extensions.DependencyInjection.Abstractions extension):\n\u002F\u002F builder.Services.AddScoped\u003CIOrderRepository, EfOrderRepository>();\n\u002F\u002F builder.Services.Decorate\u003CIOrderRepository, CachedOrderRepository>();\n```\n\n**Rule of thumb:** For a single decorator layer, the factory delegate approach is clear\nenough. For multiple layers or frequent decorator use, add Scrutor to avoid nesting\nfactory delegates.\n",{"id":82,"difficulty":14,"q":83,"a":84},"di-third-party-containers","When and how do you replace the built-in DI container with a third-party container?","The built-in container covers the vast majority of scenarios. Reach for a third-party\ncontainer (Autofac, Lamar, Grace) only when you need features it doesn't provide:\nproperty injection, convention-based registration, advanced interception, or\nchild\u002Fnested container scoping.\n\n```csharp\n\u002F\u002F Add Autofac as the service provider factory (Autofac.Extensions.DependencyInjection):\nbuilder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());\n\n\u002F\u002F Configure Autofac modules alongside standard IServiceCollection registrations:\nbuilder.Host.ConfigureContainer\u003CContainerBuilder>(containerBuilder =>\n{\n    \u002F\u002F Standard registrations still work — Autofac wraps IServiceCollection:\n    \u002F\u002F (already added via builder.Services above)\n\n    \u002F\u002F Autofac-specific features:\n    containerBuilder.RegisterType\u003CEfOrderRepository>()\n        .As\u003CIOrderRepository>()\n        .InstancePerLifetimeScope()\n        .EnableInterfaceInterceptors(); \u002F\u002F AOP interception — not in built-in\n\n    \u002F\u002F Convention-based scan and register all types in an assembly:\n    containerBuilder.RegisterAssemblyTypes(typeof(Program).Assembly)\n        .Where(t => t.Name.EndsWith(\"Service\"))\n        .AsImplementedInterfaces()\n        .InstancePerLifetimeScope();\n\n    \u002F\u002F Property injection:\n    containerBuilder.RegisterType\u003CLegacyReporter>()\n        .As\u003CIReporter>()\n        .PropertiesAutowired(); \u002F\u002F not supported by built-in container\n});\n\n\u002F\u002F Note: ASP.NET Core's IServiceCollection registrations are imported automatically.\n\u002F\u002F Third-party containers must implement IServiceProviderFactory\u003CTContainerBuilder>.\n```\n\n**Rule of thumb:** Start with the built-in container — it's fast, simple, and supports\n90% of DI patterns. Switch to a third-party container only for features the built-in\ncan't provide, and document why so the next developer understands the dependency.\n",15,null,{"description":11},"Dependency injection interview questions — IServiceCollection, constructor injection, open generics, keyed services, and common DI anti-patterns.","dotnet\u002Fdependency-injection\u002Fdi-basics","DI Basics","Dependency Injection","dependency-injection","2026-06-23","O53VLkWPA5WlI44mcxtSRzCe5D2Fi5lDZPF_9pPfV4o",[96,97,100],{"subtopic":90,"path":21,"order":20},{"subtopic":98,"path":99,"order":12},"Service Lifetimes","\u002Fdotnet\u002Fdependency-injection\u002Fservice-lifetimes",{"subtopic":101,"path":102,"order":103},"Options Pattern","\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern",3,{"path":105,"title":106},"\u002Fblog\u002Fdotnet-di-basics","Dependency Injection in .NET Core",1782244118491]