[{"data":1,"prerenderedAt":260},["ShallowReactive",2],{"topic-dotnet-dependency-injection":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Fdotnet.yml",".NET Core interview questions on the CLR, C# language features, ASP.NET Core, dependency injection, Entity Framework, and deployment — the backbone of modern Microsoft-stack backend development.","yml","dotnet",{},".NET Core",7,"frameworks\u002Fdotnet",1,"_nP5kVwZOMUX9nd4gS5Ozj5Q6zQOEmGtLONE3ue01Gw",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Fdotnet-dependency-injection.yml","The built-in DI container, service lifetimes, captive dependency bugs, and the Options pattern — topics that come up in almost every ASP.NET Core technical screen.",{},"Dependency Injection",4,"dependency-injection","topics\u002Fdotnet-dependency-injection","tpV9wR0WYmFLd-MXE7pwbf9tCeqKkpSiPt7LZb4M7V4",[25,111,185],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":37,"navigation":38,"order":13,"path":39,"questions":40,"questionsCount":103,"related":104,"seo":105,"seoDescription":106,"stem":107,"subtopic":108,"topic":19,"topicSlug":21,"updated":109,"__hash__":110},"qa\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics.md","Di Basics",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"medium","md",{},true,"\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics",[41,46,50,54,58,62,66,70,74,78,82,86,90,94,99],{"id":42,"difficulty":43,"q":44,"a":45},"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":47,"difficulty":43,"q":48,"a":49},"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":51,"difficulty":43,"q":52,"a":53},"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":55,"difficulty":35,"q":56,"a":57},"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":59,"difficulty":35,"q":60,"a":61},"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":63,"difficulty":35,"q":64,"a":65},"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":67,"difficulty":35,"q":68,"a":69},"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":71,"difficulty":35,"q":72,"a":73},"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":75,"difficulty":35,"q":76,"a":77},"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":79,"difficulty":43,"q":80,"a":81},"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":83,"difficulty":43,"q":84,"a":85},"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":87,"difficulty":35,"q":88,"a":89},"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":91,"difficulty":35,"q":92,"a":93},"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":95,"difficulty":96,"q":97,"a":98},"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":100,"difficulty":35,"q":101,"a":102},"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":32},"Dependency injection interview questions — IServiceCollection, constructor injection, open generics, keyed services, and common DI anti-patterns.","dotnet\u002Fdependency-injection\u002Fdi-basics","DI Basics","2026-06-23","O53VLkWPA5WlI44mcxtSRzCe5D2Fi5lDZPF_9pPfV4o",{"id":112,"title":113,"body":114,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":118,"navigation":38,"order":33,"path":119,"questions":120,"questionsCount":103,"related":104,"seo":181,"seoDescription":182,"stem":183,"subtopic":113,"topic":19,"topicSlug":21,"updated":109,"__hash__":184},"qa\u002Fdotnet\u002Fdependency-injection\u002Fservice-lifetimes.md","Service Lifetimes",{"type":29,"value":115,"toc":116},[],{"title":32,"searchDepth":33,"depth":33,"links":117},[],{},"\u002Fdotnet\u002Fdependency-injection\u002Fservice-lifetimes",[121,125,129,133,137,141,145,149,153,157,161,165,169,173,177],{"id":122,"difficulty":43,"q":123,"a":124},"three-lifetimes","What are the three service lifetimes in .NET Core DI and when do you use each?","The DI container supports three lifetimes that control when a service instance is\ncreated and how long it lives.\n\n```csharp\n\u002F\u002F Singleton — one instance for the entire application lifetime:\nbuilder.Services.AddSingleton\u003CICache, MemoryCache>();\n\u002F\u002F Same instance returned for every request and every constructor injection.\n\u002F\u002F Use for: stateless or thread-safe services, expensive-to-create objects,\n\u002F\u002F in-memory caches, configuration wrappers, HttpClient via IHttpClientFactory.\n\n\u002F\u002F Scoped — one instance per HTTP request (or per explicit scope):\nbuilder.Services.AddScoped\u003CIOrderService, OrderService>();\n\u002F\u002F Same instance within a single request; new instance for the next request.\n\u002F\u002F Use for: DbContext, unit-of-work objects, per-request state like current user.\n\n\u002F\u002F Transient — new instance every time the service is resolved:\nbuilder.Services.AddTransient\u003CIEmailValidator, EmailValidator>();\n\u002F\u002F New instance injected into every constructor and every GetService\u003CT>() call.\n\u002F\u002F Use for: lightweight stateless services, or services with non-thread-safe state.\n```\n\nQuick reference:\n\n| Lifetime | Created | Disposed | Shared across requests? |\n|---|---|---|---|\n| Singleton | App start | App shutdown | Yes |\n| Scoped | Per request | End of request | Within one request |\n| Transient | Per resolve | End of scope | Never |\n\n**Rule of thumb:** Default to Scoped for application services. Use Singleton only for\nthread-safe, expensive, or shared state. Use Transient for lightweight stateless helpers.\n",{"id":126,"difficulty":43,"q":127,"a":128},"singleton-details","When should you use Singleton lifetime and what are the thread-safety requirements?","**Singleton** services are instantiated once and shared across all requests for the\nlifetime of the application. Because multiple concurrent requests share the same instance,\nit **must be thread-safe**.\n\n```csharp\n\u002F\u002F Good singleton — immutable or thread-safe state:\nbuilder.Services.AddSingleton\u003CIConfiguration>(); \u002F\u002F already singleton in ASP.NET Core\nbuilder.Services.AddSingleton\u003CIHttpClientFactory>(); \u002F\u002F thread-safe, manages pool\nbuilder.Services.AddSingleton\u003CIMemoryCache, MemoryCache>(); \u002F\u002F thread-safe\n\n\u002F\u002F Good: expensive-to-create object shared across requests:\nbuilder.Services.AddSingleton\u003CIConnectionMultiplexer>(sp =>\n    ConnectionMultiplexer.Connect(sp.GetRequiredService\u003CIConfiguration>()[\"Redis\"]));\n\n\u002F\u002F Bad singleton — mutable state without synchronization:\npublic class RequestCounter \u002F\u002F registered as Singleton\n{\n    private int _count; \u002F\u002F shared across all requests — race condition!\n    public void Increment() => _count++; \u002F\u002F not thread-safe\n}\n\n\u002F\u002F Fix: use thread-safe primitives:\npublic class RequestCounter\n{\n    private long _count;\n    public void Increment() => Interlocked.Increment(ref _count);\n    public long Get() => Interlocked.Read(ref _count);\n}\n\n\u002F\u002F Bad: singleton holding a scoped dependency (captive dependency):\npublic class PricingEngine \u002F\u002F Singleton\n{\n    public PricingEngine(AppDbContext db) { } \u002F\u002F AppDbContext is Scoped — BUG\n}\n```\n\n**Rule of thumb:** If a singleton holds shared mutable state, protect it with\n`Interlocked`, `lock`, or concurrent collections. Never let a singleton hold a\nScoped service — that creates a captive dependency.\n",{"id":130,"difficulty":43,"q":131,"a":132},"scoped-details","How does Scoped lifetime work in ASP.NET Core and what is the \"scope\" boundary?","**Scoped** services get one instance per DI **scope**. In ASP.NET Core, the framework\nautomatically creates a scope for each HTTP request and disposes it when the response\ncompletes. Two injections of the same scoped service within one request share the same\ninstance.\n\n```csharp\n\u002F\u002F AppDbContext is the canonical scoped service — one connection per request:\nbuilder.Services.AddScoped\u003CAppDbContext>();\n\n\u002F\u002F Both inject the same instance within a single request:\npublic class OrderService\n{\n    public OrderService(AppDbContext db) { _db = db; }\n}\n\npublic class InventoryService\n{\n    public InventoryService(AppDbContext db) { _db = db; }\n    \u002F\u002F Same AppDbContext instance as OrderService within this request\n}\n\n\u002F\u002F Creating a scope manually (needed in background services, tests, CLI):\nusing (var scope = app.Services.CreateScope())\n{\n    var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n    await db.Database.MigrateAsync(); \u002F\u002F own scope, own connection\n} \u002F\u002F AppDbContext disposed here\n\n\u002F\u002F HttpContext.RequestServices exposes the request scope:\npublic class MyController : ControllerBase\n{\n    [HttpGet]\n    public IActionResult Get()\n    {\n        \u002F\u002F Resolved from the request scope (same as constructor injection):\n        var svc = HttpContext.RequestServices.GetRequiredService\u003CIOrderService>();\n        return Ok();\n    }\n}\n```\n\n**Rule of thumb:** Scope boundaries are your unit-of-work boundary. Register `DbContext`\nand anything that wraps a database connection as Scoped to get automatic per-request\nisolation and disposal.\n",{"id":134,"difficulty":43,"q":135,"a":136},"transient-details","When should you use Transient lifetime and what are its trade-offs?","**Transient** services are created fresh on every resolve — every constructor injection\nand every `GetService\u003CT>()` call yields a new instance. They're disposed at the end of\nthe enclosing scope (usually the HTTP request).\n\n```csharp\n\u002F\u002F Transient registration:\nbuilder.Services.AddTransient\u003CIEmailValidator, RegexEmailValidator>();\n\n\u002F\u002F Demonstration — each injection gets its own instance:\npublic class SignupService\n{\n    private readonly IEmailValidator _v1;\n    private readonly IEmailValidator _v2;\n\n    public SignupService(IEmailValidator v1, IEmailValidator v2)\n    {\n        \u002F\u002F v1 and v2 are different RegexEmailValidator instances\n        Console.WriteLine(ReferenceEquals(v1, v2)); \u002F\u002F false\n    }\n}\n\n\u002F\u002F Trade-offs:\n\u002F\u002F Safe for non-thread-safe objects — each consumer gets its own copy\n\u002F\u002F Good for lightweight, stateless helpers\n\u002F\u002F Can create many instances per request — avoid for expensive-to-construct types\n\u002F\u002F If the service holds resources (connections, file handles), disposal overhead\n\u002F\u002F multiplies with each resolve\n\n\u002F\u002F IDisposable transients — the scope owns disposal:\npublic class CsvWriter : IDisposable\n{\n    private readonly StreamWriter _writer;\n    public CsvWriter() => _writer = new StreamWriter(\"output.csv\");\n    public void Dispose() => _writer.Dispose();\n}\n\u002F\u002F Disposed at end of request scope — the container tracks IDisposable transients\n```\n\n**Rule of thumb:** Use Transient for cheap, stateless, non-thread-safe services.\nPrefer Singleton for stateless thread-safe services — Transient's per-resolve cost\nadds up fast in high-throughput endpoints.\n",{"id":138,"difficulty":35,"q":139,"a":140},"captive-dependency","What is a captive dependency and how do you detect and fix it?","A **captive dependency** occurs when a service with a longer lifetime holds a reference\nto a service with a shorter lifetime. The short-lived service effectively becomes\nlong-lived — living beyond its intended scope and potentially sharing state across\nrequests.\n\n```csharp\n\u002F\u002F Captive dependency: Singleton captures a Scoped service\npublic class ProductCache  \u002F\u002F Singleton\n{\n    private readonly AppDbContext _db; \u002F\u002F Scoped — lives per request\n\n    public ProductCache(AppDbContext db)\n    {\n        _db = db; \u002F\u002F db now lives forever — same instance across ALL requests\n        \u002F\u002F Any request-specific state in db leaks across requests → data corruption\n    }\n}\n\n\u002F\u002F Detection — enable ValidateScopes in development:\nbuilder.Host.UseDefaultServiceProvider(options =>\n{\n    options.ValidateScopes = app.Environment.IsDevelopment();\n});\n\u002F\u002F Without this, the bug silently causes intermittent data corruption in production.\n\n\u002F\u002F Fix 1: resolve scoped service per operation via 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        \u002F\u002F scope and db disposed at end of using block\n    }\n}\n\n\u002F\u002F Fix 2: change the lifetime — if the class truly needs Scoped, register it Scoped:\nbuilder.Services.AddScoped\u003CProductCache>();\n\u002F\u002F Now it gets a fresh AppDbContext per request automatically\n```\n\n**Rule of thumb:** Captive dependencies are the most dangerous DI lifetime bug —\nthey corrupt request state silently. Enable `ValidateScopes` in development to catch\nthem at startup.\n",{"id":142,"difficulty":35,"q":143,"a":144},"iscope-factory","What is IServiceScopeFactory and when do you need it?","**`IServiceScopeFactory`** creates explicit DI scopes on demand — essential for any\ncode that runs outside the ASP.NET Core request pipeline (background services, hosted\nservices, console programs) where no automatic scope exists.\n\n```csharp\n\u002F\u002F Background service — no HTTP request scope exists:\npublic class DataSyncHostedService : BackgroundService\n{\n    private readonly IServiceScopeFactory _scopeFactory;\n    private readonly ILogger\u003CDataSyncHostedService> _logger;\n\n    public DataSyncHostedService(\n        IServiceScopeFactory scopeFactory,\n        ILogger\u003CDataSyncHostedService> logger)\n    {\n        _scopeFactory = scopeFactory;\n        _logger = logger;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            \u002F\u002F Create a fresh scope for each sync cycle:\n            using (var scope = _scopeFactory.CreateScope())\n            {\n                var db       = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n                var syncSvc  = scope.ServiceProvider.GetRequiredService\u003CISyncService>();\n\n                await syncSvc.SyncAsync(stoppingToken);\n                \u002F\u002F db and syncSvc disposed when scope is disposed (end of using)\n            }\n\n            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);\n        }\n    }\n}\n```\n\n`IServiceScopeFactory` is itself registered as a Singleton by the framework, so it can\nbe injected into Singleton services safely. The scopes it creates are independent and\nproperly bounded.\n\n**Rule of thumb:** Any Singleton that needs a Scoped service should inject\n`IServiceScopeFactory` and create a new scope per operation. Never inject Scoped\nservices directly into a Singleton constructor.\n",{"id":146,"difficulty":35,"q":147,"a":148},"disposal","How does the DI container handle IDisposable and IAsyncDisposable services?","The DI container **automatically disposes** services that implement `IDisposable` or\n`IAsyncDisposable` when their owning scope ends — Singleton at app shutdown, Scoped\nand Transient at the end of the request scope.\n\n```csharp\npublic class DatabaseConnection : IDisposable, IAsyncDisposable\n{\n    private SqlConnection _conn;\n\n    public DatabaseConnection(string connectionString)\n        => _conn = new SqlConnection(connectionString);\n\n    public void Dispose()\n    {\n        _conn?.Dispose();\n        _conn = null;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (_conn is not null)\n        {\n            await _conn.DisposeAsync();\n            _conn = null;\n        }\n    }\n}\n\nbuilder.Services.AddScoped\u003CDatabaseConnection>();\n\u002F\u002F Disposed (DisposeAsync preferred) at the end of each HTTP request automatically.\n\n\u002F\u002F Singletons are disposed at app shutdown:\nbuilder.Services.AddSingleton\u003CIConnectionMultiplexer>(sp =>\n    ConnectionMultiplexer.Connect(\"localhost\"));\n\u002F\u002F ConnectionMultiplexer.Dispose() called when app.StopAsync() completes.\n\n\u002F\u002F Transients are tracked by the scope and disposed with it:\n\u002F\u002F If a Transient IDisposable is resolved from the root scope (e.g., app.Services),\n\u002F\u002F it won't be disposed until app shutdown — this is a memory leak.\n\u002F\u002F Always resolve Disposable Transients from a child scope.\n```\n\n**Rule of thumb:** Let the container own disposal — never call `Dispose()` on injected\nservices yourself. For root-scope transients that are `IDisposable`, create an explicit\nchild scope so disposal happens promptly.\n",{"id":150,"difficulty":35,"q":151,"a":152},"background-service-scopes","Why can't a BackgroundService inject Scoped services directly?","**`BackgroundService`** is registered as a **Singleton** (it runs for the app's lifetime).\nInjecting a Scoped service directly into its constructor creates a **captive dependency** —\nthe Scoped service becomes effectively Singleton and doesn't get disposed per request.\n\n```csharp\n\u002F\u002F WRONG — captive dependency; AppDbContext lives for the app lifetime:\npublic class OrderProcessor : BackgroundService\n{\n    private readonly AppDbContext _db; \u002F\u002F Scoped — wrong lifetime here\n\n    public OrderProcessor(AppDbContext db) \u002F\u002F container throws or creates a captive\n    {\n        _db = db;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken ct)\n    {\n        \u002F\u002F _db is reused across ALL processing cycles — EF change tracker corrupts\n        while (!ct.IsCancellationRequested)\n            await ProcessNextOrderAsync(_db);\n    }\n}\n\n\u002F\u002F CORRECT — create a scope per work unit:\npublic class OrderProcessor : BackgroundService\n{\n    private readonly IServiceScopeFactory _factory;\n\n    public OrderProcessor(IServiceScopeFactory factory) => _factory = factory;\n\n    protected override async Task ExecuteAsync(CancellationToken ct)\n    {\n        while (!ct.IsCancellationRequested)\n        {\n            using var scope = _factory.CreateScope();\n            var db    = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n            var queue = scope.ServiceProvider.GetRequiredService\u003CIOrderQueue>();\n\n            await ProcessNextOrderAsync(db, queue);\n            \u002F\u002F db and queue properly disposed after each iteration\n        }\n    }\n}\n```\n\n**Rule of thumb:** `BackgroundService` constructors should only accept Singleton services\nplus `IServiceScopeFactory`. Create a new scope for every unit of work that needs Scoped\nservices (DbContext, unit-of-work, per-operation state).\n",{"id":154,"difficulty":35,"q":155,"a":156},"root-scope","What is the root scope in .NET DI and why is it a source of memory leaks?","The **root scope** is the `IServiceProvider` created by `builder.Build()`. It's the\ntop-level container that lives for the entire application lifetime. Resolving Scoped or\nTransient `IDisposable` services from the root scope leaks them until app shutdown.\n\n```csharp\nvar app = builder.Build();\n\n\u002F\u002F Resolving Scoped service from root scope — leaks until app shutdown:\nvar db = app.Services.GetRequiredService\u003CAppDbContext>();\n\u002F\u002F db is Scoped, but the root scope never ends → memory + connection leak\n\n\u002F\u002F Always create a child scope for one-off operations:\nusing (var scope = app.Services.CreateScope())\n{\n    var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n    await db.Database.MigrateAsync(); \u002F\u002F db disposed at end of using block\n}\n\n\u002F\u002F With ValidateScopes = true, resolving a Scoped service from root throws:\nbuilder.Host.UseDefaultServiceProvider(options =>\n{\n    options.ValidateScopes = app.Environment.IsDevelopment();\n});\n\u002F\u002F InvalidOperationException: Cannot resolve scoped service from root provider.\n\n\u002F\u002F Safe: resolving Singleton from root is always fine:\nvar config = app.Services.GetRequiredService\u003CIConfiguration>(); \u002F\u002F OK\nvar cache  = app.Services.GetRequiredService\u003CIMemoryCache>();   \u002F\u002F OK\n```\n\n**Rule of thumb:** Never call `app.Services.GetRequiredService\u003CT>()` for Scoped or\nTransient services. Always wrap one-off resolutions in `CreateScope()`. Enable\n`ValidateScopes` to catch root-scope violations at startup in development.\n",{"id":158,"difficulty":35,"q":159,"a":160},"blazor-lifetimes","How do service lifetimes differ in Blazor Server vs Blazor WebAssembly?","Blazor's DI model differs from ASP.NET Core's request pipeline because Blazor Server\nkeeps a persistent SignalR circuit per browser tab, and Blazor WebAssembly runs entirely\nin the browser.\n\n```csharp\n\u002F\u002F Blazor Server — \"Scoped\" = per circuit (browser tab session), not per HTTP request:\nbuilder.Services.AddScoped\u003CIUserState, UserState>();\n\u002F\u002F Same instance across all component renders within one browser tab.\n\u002F\u002F A new tab → new circuit → new IUserState instance.\n\u002F\u002F This is a LONGER lifetime than ASP.NET Core Scoped (which is per-request).\n\n\u002F\u002F Singleton in Blazor Server — shared across ALL connected browser tabs!\nbuilder.Services.AddSingleton\u003CISharedCache, MemoryCache>();\n\u002F\u002F All users share this — only safe for truly shared, thread-safe state.\n\n\u002F\u002F Blazor WebAssembly — no server; runs in browser:\n\u002F\u002F Singleton = lives until the browser tab is closed or refreshed.\n\u002F\u002F Scoped    = same as Singleton in WASM (one scope for the whole app lifetime).\n\u002F\u002F Transient = new instance per injection, just like everywhere else.\nbuilder.Services.AddSingleton\u003CICartService, CartService>(); \u002F\u002F per tab in WASM\n\n\u002F\u002F Per-component transient state:\nbuilder.Services.AddTransient\u003CIComponentState, ComponentState>();\n\u002F\u002F New instance per component that injects it — use [CascadingParameter] instead\n\u002F\u002F for parent-to-child state sharing in Blazor.\n```\n\n**Rule of thumb:** In Blazor Server, treat Scoped as per-user-session and Singleton\nas shared-across-all-users. Never store user-specific state in a Singleton in Blazor Server.\n",{"id":162,"difficulty":35,"q":163,"a":164},"lifetime-mismatch-detection","How do you detect and prevent lifetime mismatch bugs at development time?","Lifetime mismatches are subtle bugs — they often don't fail immediately but cause data\ncorruption or state leaks in production. .NET provides built-in tooling to catch them.\n\n```csharp\n\u002F\u002F 1. Enable ValidateScopes + ValidateOnBuild (development only):\nbuilder.Host.UseDefaultServiceProvider((ctx, options) =>\n{\n    options.ValidateOnBuild = ctx.HostingEnvironment.IsDevelopment();\n    options.ValidateScopes  = ctx.HostingEnvironment.IsDevelopment();\n});\n\n\u002F\u002F 2. Write integration tests that build the real container:\npublic class DependencyInjectionTests\n{\n    [Fact]\n    public void AllServicesResolvable()\n    {\n        \u002F\u002F Use the real IServiceCollection from your app:\n        var services = new ServiceCollection();\n        Startup.ConfigureServices(services);\n\n        using var provider = services.BuildServiceProvider(\n            new ServiceProviderOptions\n            {\n                ValidateOnBuild = true,\n                ValidateScopes  = true,\n            });\n\n        \u002F\u002F If any registration is broken, BuildServiceProvider throws here:\n        Assert.NotNull(provider.CreateScope()\n            .ServiceProvider.GetRequiredService\u003CIOrderService>());\n    }\n}\n\n\u002F\u002F 3. Use the ASP.NET Core test host (validates real DI graph):\nawait using var factory = new WebApplicationFactory\u003CProgram>()\n    .WithWebHostBuilder(builder =>\n        builder.UseEnvironment(\"Development\")); \u002F\u002F triggers ValidateScopes\nvar client = factory.CreateClient();\n\u002F\u002F First request will throw if DI is misconfigured\n```\n\n**Rule of thumb:** Make DI validation part of CI — either via integration tests that\nbuild the real container or by running the app in Development mode with `ValidateScopes`\nand `ValidateOnBuild` enabled.\n",{"id":166,"difficulty":35,"q":167,"a":168},"transient-vs-singleton-perf","What are the performance implications of choosing Transient vs Singleton for stateless services?","**Singleton** is almost always faster for stateless services — allocation, garbage\ncollection, and constructor cost happen once at startup rather than on every resolve.\nTransient multiplies that cost by every request and every inject site.\n\n```csharp\n\u002F\u002F Scenario: a stateless email validator resolved thousands of times per second.\n\n\u002F\u002F Transient — new RegexEmailValidator() on every resolution:\nbuilder.Services.AddTransient\u003CIEmailValidator, RegexEmailValidator>();\n\u002F\u002F Cost per resolution: heap allocation + Regex compilation (if not pre-compiled)\n\u002F\u002F   + GC pressure accumulates under high throughput.\n\n\u002F\u002F Singleton — one instance created at startup, shared for app lifetime:\nbuilder.Services.AddSingleton\u003CIEmailValidator, RegexEmailValidator>();\n\u002F\u002F Cost per resolution: dictionary lookup only — no allocation.\n\u002F\u002F Safe because RegexEmailValidator has no mutable state.\n\n\u002F\u002F Benchmark illustration (not real numbers — for illustration only):\n\u002F\u002F 1M resolutions: Transient ~300ms | Singleton ~5ms\n\n\u002F\u002F Transient is correct when the service holds non-thread-safe mutable state:\npublic class CsvBuilder \u002F\u002F not thread-safe — each caller needs its own instance\n{\n    private readonly StringBuilder _sb = new();\n    public void AddRow(string[] cells) => _sb.AppendJoin(',', cells);\n    public string Build() => _sb.ToString();\n}\nbuilder.Services.AddTransient\u003CCsvBuilder>(); \u002F\u002F correct: new per injection\n\n\u002F\u002F Singleton is wrong for non-thread-safe types:\nbuilder.Services.AddSingleton\u003CCsvBuilder>(); \u002F\u002F Bad: shared StringBuilder corrupts\n```\n\n**Rule of thumb:** For stateless, thread-safe services, prefer Singleton. The allocation\ncost of Transient is negligible for lightweight types, but it becomes measurable in\nhot paths. Profile before over-optimizing, but design stateless helpers to be Singleton-safe.\n",{"id":170,"difficulty":96,"q":171,"a":172},"scoped-outside-request","How do you use Scoped services outside the ASP.NET Core request pipeline?","Outside the HTTP request pipeline — CLI tools, unit tests, EF migrations, integration\ntest setup, worker services — no scope exists automatically. You must create one\nexplicitly via `IServiceScopeFactory` or `IServiceProvider.CreateScope()`.\n\n```csharp\n\u002F\u002F Pattern 1: one-off operation after app.Build() (e.g., run EF migrations):\nvar app = builder.Build();\n\nusing (var scope = app.Services.CreateScope())\n{\n    var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n    await db.Database.MigrateAsync();\n    \u002F\u002F scope and db disposed at end of using block\n}\n\nawait app.RunAsync();\n\n\u002F\u002F Pattern 2: integration test setup with WebApplicationFactory:\npublic class OrdersIntegrationTest : IClassFixture\u003CWebApplicationFactory\u003CProgram>>\n{\n    private readonly WebApplicationFactory\u003CProgram> _factory;\n\n    public OrdersIntegrationTest(WebApplicationFactory\u003CProgram> factory)\n        => _factory = factory;\n\n    [Fact]\n    public async Task SeedDatabase_ThenQueryViaApi()\n    {\n        \u002F\u002F Create a scope for direct DB access in the test:\n        using var scope = _factory.Services.CreateScope();\n        var db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\n        db.Orders.Add(new Order { Id = 1, Total = 99.99m });\n        await db.SaveChangesAsync();\n\n        \u002F\u002F Now hit the real API endpoint:\n        var client   = _factory.CreateClient();\n        var response = await client.GetAsync(\"\u002Forders\u002F1\");\n        response.EnsureSuccessStatusCode();\n    }\n}\n\n\u002F\u002F Pattern 3: console \u002F worker service (no HTTP pipeline at all):\nvar host = Host.CreateDefaultBuilder(args)\n    .ConfigureServices(services =>\n    {\n        services.AddScoped\u003CAppDbContext>();\n        services.AddScoped\u003CIDataImporter, CsvDataImporter>();\n    })\n    .Build();\n\nusing (var scope = host.Services.CreateScope())\n{\n    var importer = scope.ServiceProvider.GetRequiredService\u003CIDataImporter>();\n    await importer.ImportAsync(\"data.csv\");\n}\n```\n\n**Rule of thumb:** Any code that runs outside a request scope must create its own scope\nusing `CreateScope()`. Dispose the scope as soon as the unit of work is complete so\nScoped services (DbContext, connections) are released promptly.\n",{"id":174,"difficulty":96,"q":175,"a":176},"singleton-thread-safety-patterns","What patterns ensure thread safety in Singleton services?","Singleton services are shared across all concurrent requests, so any mutable shared\nstate must be protected. The right tool depends on the access pattern.\n\n```csharp\n\u002F\u002F Pattern 1: immutability — no synchronization needed:\npublic class RegionConfig \u002F\u002F Singleton\n{\n    \u002F\u002F Populated once at construction; never mutated afterward:\n    private readonly IReadOnlyDictionary\u003Cstring, string> _regions;\n\n    public RegionConfig(IConfiguration config)\n        => _regions = config.GetSection(\"Regions\")\n            .GetChildren()\n            .ToDictionary(s => s.Key, s => s.Value ?? \"\");\n\n    public string? GetEndpoint(string region)\n        => _regions.TryGetValue(region, out var url) ? url : null;\n}\n\n\u002F\u002F Pattern 2: Interlocked for counters and simple values:\npublic class RequestMetrics \u002F\u002F Singleton\n{\n    private long _totalRequests;\n    private long _failedRequests;\n\n    public void RecordSuccess() => Interlocked.Increment(ref _totalRequests);\n    public void RecordFailure()\n    {\n        Interlocked.Increment(ref _totalRequests);\n        Interlocked.Increment(ref _failedRequests);\n    }\n    public long Total   => Interlocked.Read(ref _totalRequests);\n    public long Failures => Interlocked.Read(ref _failedRequests);\n}\n\n\u002F\u002F Pattern 3: ConcurrentDictionary for concurrent read-write maps:\npublic class InMemoryCache\u003CTKey, TValue> where TKey : notnull \u002F\u002F Singleton\n{\n    private readonly ConcurrentDictionary\u003CTKey, TValue> _store = new();\n\n    public TValue GetOrAdd(TKey key, Func\u003CTKey, TValue> factory)\n        => _store.GetOrAdd(key, factory); \u002F\u002F thread-safe by design\n\n    public bool TryRemove(TKey key, out TValue? value)\n        => _store.TryRemove(key, out value);\n}\n\n\u002F\u002F Pattern 4: ReaderWriterLockSlim for infrequent writes, frequent reads:\npublic class RoutingTable \u002F\u002F Singleton\n{\n    private Dictionary\u003Cstring, string> _routes = new();\n    private readonly ReaderWriterLockSlim _lock = new();\n\n    public string? Resolve(string path)\n    {\n        _lock.EnterReadLock();\n        try   { return _routes.TryGetValue(path, out var dest) ? dest : null; }\n        finally { _lock.ExitReadLock(); }\n    }\n\n    public void Reload(Dictionary\u003Cstring, string> newRoutes)\n    {\n        _lock.EnterWriteLock();\n        try   { _routes = newRoutes; }\n        finally { _lock.ExitWriteLock(); }\n    }\n}\n```\n\n**Rule of thumb:** Prefer immutable state for singletons — no locks needed. When you\nneed mutable shared state, use `Interlocked` for scalars, `ConcurrentDictionary` for\nmaps, and `ReaderWriterLockSlim` for rarely-written, frequently-read structures.\n",{"id":178,"difficulty":35,"q":179,"a":180},"scoped-lifetime-grpc","How does service lifetime work in gRPC services with ASP.NET Core?","gRPC services in ASP.NET Core are treated like controllers — the framework creates a\n**new instance per RPC call** (equivalent to per-request). DI lifetime rules apply\nthe same way as in HTTP middleware and controllers.\n\n```csharp\n\u002F\u002F gRPC service — one instance per RPC call by default:\npublic class OrderGrpcService : OrderService.OrderServiceBase\n{\n    private readonly IOrderRepository _orders; \u002F\u002F Scoped — safe; new per RPC call\n    private readonly ILogger\u003COrderGrpcService> _logger;\n\n    public OrderGrpcService(IOrderRepository orders, ILogger\u003COrderGrpcService> logger)\n    {\n        _orders = orders;\n        _logger = logger;\n    }\n\n    public override async Task\u003COrderReply> GetOrder(\n        OrderRequest request, ServerCallContext context)\n    {\n        _logger.LogInformation(\"GetOrder called for id={Id}\", request.Id);\n        var order = await _orders.GetByIdAsync(request.Id);\n        return order is null\n            ? throw new RpcException(new Status(StatusCode.NotFound, \"Order not found\"))\n            : new OrderReply { Id = order.Id, Total = (double)order.Total };\n    }\n}\n\n\u002F\u002F Registration — same as any ASP.NET Core service:\nbuilder.Services.AddScoped\u003CIOrderRepository, EfOrderRepository>();\nbuilder.Services.AddGrpc();\napp.MapGrpcService\u003COrderGrpcService>();\n\n\u002F\u002F Streaming RPCs — the single service instance handles the entire stream:\n\u002F\u002F For server-streaming methods, the same Scoped IOrderRepository instance is used\n\u002F\u002F throughout the stream lifetime — which is the duration of the RPC call, not per message.\n\n\u002F\u002F Warning: Singleton state shared across all concurrent gRPC calls — same rules as HTTP:\nbuilder.Services.AddSingleton\u003CIGrpcMetrics, GrpcMetrics>(); \u002F\u002F must be thread-safe\n```\n\n**Rule of thumb:** In gRPC services, treat each RPC call as an HTTP request.\nScoped services are safe because the framework creates a fresh DI scope per call.\nSingleton services must still be thread-safe — concurrent streams share them.\n",{"description":32},"Service lifetime interview questions — Singleton vs Scoped vs Transient, captive dependencies, IServiceScopeFactory, disposal, and ValidateScopes.","dotnet\u002Fdependency-injection\u002Fservice-lifetimes","Hkr_1apWc4rCJ3x7rdTiH-fvfTFNci5Uh5mZdiXes7k",{"id":186,"title":187,"body":188,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":192,"navigation":38,"order":193,"path":194,"questions":195,"questionsCount":103,"related":104,"seo":256,"seoDescription":257,"stem":258,"subtopic":187,"topic":19,"topicSlug":21,"updated":109,"__hash__":259},"qa\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern.md","Options Pattern",{"type":29,"value":189,"toc":190},[],{"title":32,"searchDepth":33,"depth":33,"links":191},[],{},3,"\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern",[196,200,204,208,212,216,220,224,228,232,236,240,244,248,252],{"id":197,"difficulty":43,"q":198,"a":199},"options-what-is","What is the options pattern in .NET Core and why use it instead of IConfiguration directly?","The **options pattern** provides a strongly-typed, validated, and injectable wrapper\naround configuration sections. Instead of accessing raw string keys from `IConfiguration`,\nyou bind a configuration section to a POCO class and inject `IOptions\u003CT>`.\n\n```csharp\n\u002F\u002F Define a settings class — plain C# record or class:\npublic class SmtpSettings\n{\n    public string Host    { get; init; } = \"\";\n    public int    Port    { get; init; } = 587;\n    public bool   UseTls  { get; init; } = true;\n    public string ApiKey  { get; init; } = \"\";\n}\n\n\u002F\u002F appsettings.json:\n\u002F\u002F {\n\u002F\u002F \"Smtp\": { \"Host\": \"smtp.sendgrid.net\", \"Port\": 587, \"ApiKey\": \"SG.xxx\" }\n\u002F\u002F }\n\n\u002F\u002F Registration — binds \"Smtp\" section to SmtpSettings:\nbuilder.Services.Configure\u003CSmtpSettings>(\n    builder.Configuration.GetSection(\"Smtp\"));\n\n\u002F\u002F Injection — no raw IConfiguration in application code:\npublic class EmailService\n{\n    private readonly SmtpSettings _settings;\n\n    public EmailService(IOptions\u003CSmtpSettings> options)\n    {\n        _settings = options.Value; \u002F\u002F strongly typed; compile-time property names\n    }\n\n    public Task SendAsync(string to, string subject, string body)\n        => SendViaSMTP(_settings.Host, _settings.Port, _settings.ApiKey, to, subject, body);\n}\n```\n\nBenefits over raw `IConfiguration`:\n- **Compile-time safety** — property names checked by the compiler.\n- **Testability** — inject `Options.Create(new SmtpSettings { ... })` in tests.\n- **Validation** — validate settings at startup, not at first use.\n- **Decoupling** — application code has no knowledge of where settings come from.\n\n**Rule of thumb:** Never inject `IConfiguration` into application services. Bind it to a\ntyped settings class at startup and inject `IOptions\u003CT>` instead.\n",{"id":201,"difficulty":35,"q":202,"a":203},"ioptions-vs-snapshot-vs-monitor","What is the difference between IOptions\u003CT>, IOptionsSnapshot\u003CT>, and IOptionsMonitor\u003CT>?","The three interfaces differ in **when** they read configuration and **how** they reflect\nruntime changes.\n\n```csharp\n\u002F\u002F IOptions\u003CT> — Singleton; reads config once at startup; never reloads:\npublic class EmailService\n{\n    public EmailService(IOptions\u003CSmtpSettings> options)\n    {\n        var settings = options.Value; \u002F\u002F same object for app lifetime; no live reloads\n    }\n}\nbuilder.Services.AddSingleton\u003CEmailService>(); \u002F\u002F safe — Singleton can use IOptions\u003CT>\n\n\u002F\u002F IOptionsSnapshot\u003CT> — Scoped; re-reads config on each HTTP request:\npublic class PricingService\n{\n    public PricingService(IOptionsSnapshot\u003CPricingSettings> options)\n    {\n        var settings = options.Value; \u002F\u002F fresh snapshot per request; reflects reloads\n    }\n}\n\u002F\u002F Cannot be injected into Singleton services — Scoped lifetime.\n\n\u002F\u002F IOptionsMonitor\u003CT> — Singleton; fires a callback when config changes:\npublic class FeatureFlagService\n{\n    private FeatureFlags _current;\n\n    public FeatureFlagService(IOptionsMonitor\u003CFeatureFlags> monitor)\n    {\n        _current = monitor.CurrentValue;  \u002F\u002F read current value\n        monitor.OnChange(updated =>       \u002F\u002F callback on any reload\n        {\n            _current = updated;\n            Console.WriteLine(\"Feature flags reloaded\");\n        });\n    }\n}\nbuilder.Services.AddSingleton\u003CFeatureFlagService>(); \u002F\u002F safe — IOptionsMonitor is Singleton\n```\n\n| Interface | Lifetime | Reloads? | Use when |\n|---|---|---|---|\n| `IOptions\u003CT>` | Singleton | Never | Static config; inject into any lifetime |\n| `IOptionsSnapshot\u003CT>` | Scoped | Per request | Per-request fresh values; not in singletons |\n| `IOptionsMonitor\u003CT>` | Singleton | On change + callback | Reactive updates; safe in singletons |\n\n**Rule of thumb:** Use `IOptions\u003CT>` for static config. Use `IOptionsSnapshot\u003CT>` for\nper-request values in scoped services. Use `IOptionsMonitor\u003CT>` for live reloads in singletons.\n",{"id":205,"difficulty":43,"q":206,"a":207},"bind-from-config","How do you bind a configuration section to a typed options class?","Three equivalent patterns are available — `Configure`, `Bind`, and the newer\n`AddOptions\u003CT>().BindConfiguration()` builder style.\n\n```csharp\n\u002F\u002F appsettings.json:\n\u002F\u002F {\n\u002F\u002F \"Database\": { \"Host\": \"db.example.com\", \"Port\": 5432, \"MaxPoolSize\": 20 }\n\u002F\u002F }\n\npublic class DatabaseSettings\n{\n    public string Host        { get; set; } = \"localhost\";\n    public int    Port        { get; set; } = 5432;\n    public int    MaxPoolSize { get; set; } = 10;\n}\n\n\u002F\u002F Style 1 — Configure (most common):\nbuilder.Services.Configure\u003CDatabaseSettings>(\n    builder.Configuration.GetSection(\"Database\"));\n\n\u002F\u002F Style 2 — Bind directly to a pre-built instance:\nvar dbSettings = new DatabaseSettings();\nbuilder.Configuration.GetSection(\"Database\").Bind(dbSettings);\nbuilder.Services.AddSingleton(dbSettings); \u002F\u002F inject as a concrete instance\n\n\u002F\u002F Style 3 — AddOptions\u003CT> builder (supports validation and PostConfigure fluently):\nbuilder.Services.AddOptions\u003CDatabaseSettings>()\n    .BindConfiguration(\"Database\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart();\n\n\u002F\u002F Injecting the typed options:\npublic class DatabaseFactory\n{\n    private readonly DatabaseSettings _settings;\n\n    public DatabaseFactory(IOptions\u003CDatabaseSettings> options)\n        => _settings = options.Value;\n\n    public NpgsqlConnection CreateConnection()\n        => new($\"Host={_settings.Host};Port={_settings.Port}\");\n}\n```\n\n**Rule of thumb:** Use `AddOptions\u003CT>().BindConfiguration()` when you also need\nvalidation — it chains `.ValidateDataAnnotations()` and `.ValidateOnStart()` cleanly.\nUse `Configure\u003CT>(section)` for simple bindings.\n",{"id":209,"difficulty":35,"q":210,"a":211},"named-options","What are named options and when are they useful?","**Named options** allow multiple configurations of the same options type under distinct\nnames — useful when the same service class serves different logical contexts (e.g.,\nmultiple email providers, multiple external APIs).\n\n```csharp\npublic class ApiClientSettings\n{\n    public string BaseUrl { get; set; } = \"\";\n    public string ApiKey  { get; set; } = \"\";\n    public int    TimeoutMs { get; set; } = 5000;\n}\n\n\u002F\u002F appsettings.json:\n\u002F\u002F {\n\u002F\u002F \"Stripe\": { \"BaseUrl\": \"https:\u002F\u002Fapi.stripe.com\", \"ApiKey\": \"sk_live_...\" },\n\u002F\u002F \"Sendgrid\": { \"BaseUrl\": \"https:\u002F\u002Fapi.sendgrid.com\", \"ApiKey\": \"SG.xxx\" }\n\u002F\u002F }\n\n\u002F\u002F Register with names:\nbuilder.Services.Configure\u003CApiClientSettings>(\"Stripe\",\n    builder.Configuration.GetSection(\"Stripe\"));\nbuilder.Services.Configure\u003CApiClientSettings>(\"Sendgrid\",\n    builder.Configuration.GetSection(\"Sendgrid\"));\n\n\u002F\u002F Resolve by name using IOptionsSnapshot or IOptionsMonitor:\npublic class PaymentService\n{\n    private readonly ApiClientSettings _stripe;\n\n    public PaymentService(IOptionsSnapshot\u003CApiClientSettings> options)\n    {\n        _stripe = options.Get(\"Stripe\");    \u002F\u002F named resolution\n    }\n}\n\npublic class EmailService\n{\n    private readonly ApiClientSettings _sendgrid;\n\n    public EmailService(IOptionsMonitor\u003CApiClientSettings> monitor)\n    {\n        _sendgrid = monitor.Get(\"Sendgrid\"); \u002F\u002F named resolution\n    }\n}\n\n\u002F\u002F IOptions\u003CT>.Value always returns the unnamed (default) instance:\n\u002F\u002F options.Value == options.Get(Options.DefaultName) == options.Get(\"\")\n```\n\n**Rule of thumb:** Use named options when the same settings shape maps to multiple\nlogical configs (two payment providers, three S3 buckets). Pair with `IOptionsSnapshot`\nor `IOptionsMonitor` to call `.Get(\"name\")`.\n",{"id":213,"difficulty":35,"q":214,"a":215},"options-validation","How do you validate options at startup and what approaches are available?",".NET supports three validation approaches: **Data Annotations**, **IValidateOptions\u003CT>**\n(custom logic), and **delegate validation**. All can be triggered at startup with\n`ValidateOnStart()`.\n\n```csharp\n\u002F\u002F Approach 1 — Data Annotations (simplest):\npublic class SmtpSettings\n{\n    [Required]\n    public string Host { get; set; } = \"\";\n\n    [Range(1, 65535)]\n    public int Port { get; set; } = 587;\n\n    [Required, MinLength(20)]\n    public string ApiKey { get; set; } = \"\";\n}\n\nbuilder.Services.AddOptions\u003CSmtpSettings>()\n    .BindConfiguration(\"Smtp\")\n    .ValidateDataAnnotations()  \u002F\u002F validates using System.ComponentModel.DataAnnotations\n    .ValidateOnStart();          \u002F\u002F throws at startup if validation fails\n\n\u002F\u002F Approach 2 — IValidateOptions\u003CT> (complex cross-property rules):\npublic class SmtpSettingsValidator : IValidateOptions\u003CSmtpSettings>\n{\n    public ValidateOptionsResult Validate(string? name, SmtpSettings options)\n    {\n        if (options.UseTls && options.Port == 25)\n            return ValidateOptionsResult.Fail(\n                \"TLS is enabled but port 25 is for unencrypted SMTP. Use 587 or 465.\");\n\n        return ValidateOptionsResult.Success;\n    }\n}\n\nbuilder.Services.AddSingleton\u003CIValidateOptions\u003CSmtpSettings>, SmtpSettingsValidator>();\n\n\u002F\u002F Approach 3 — inline delegate:\nbuilder.Services.AddOptions\u003CSmtpSettings>()\n    .BindConfiguration(\"Smtp\")\n    .Validate(s => !string.IsNullOrEmpty(s.Host), \"Smtp:Host is required\")\n    .ValidateOnStart();\n```\n\nWithout `ValidateOnStart()`, validation only runs the first time `options.Value` is\naccessed — which could be on the first production request rather than at startup.\n\n**Rule of thumb:** Always pair validation with `ValidateOnStart()` so misconfigured\nenvironments fail immediately with a clear error, not silently under load.\n",{"id":217,"difficulty":35,"q":218,"a":219},"configure-vs-postconfigure","What is the difference between Configure\u003CT> and PostConfigure\u003CT>?","**`Configure\u003CT>`** sets option values. **`PostConfigure\u003CT>`** runs *after* all\n`Configure\u003CT>` calls and is used to override, sanitize, or augment values — useful\nin framework\u002Flibrary code that needs to ensure invariants regardless of what the app set.\n\n```csharp\n\u002F\u002F Base configuration from appsettings:\nbuilder.Services.Configure\u003CCacheSettings>(\n    builder.Configuration.GetSection(\"Cache\"));\n\u002F\u002F CacheSettings.MaxSize = 500 (from appsettings)\n\n\u002F\u002F Library code sets a sensible default if not configured:\nbuilder.Services.Configure\u003CCacheSettings>(settings =>\n{\n    if (settings.MaxSize == 0)\n        settings.MaxSize = 100; \u002F\u002F only sets if not already configured — check needed\n});\n\n\u002F\u002F PostConfigure — runs last; overrides whatever Configure set:\nbuilder.Services.PostConfigure\u003CCacheSettings>(settings =>\n{\n    \u002F\u002F Enforce a hard cap regardless of what appsettings or Configure said:\n    if (settings.MaxSize > 10_000)\n        settings.MaxSize = 10_000;\n\n    \u002F\u002F Ensure derived values are consistent:\n    settings.EvictionBatchSize = Math.Min(settings.EvictionBatchSize, settings.MaxSize \u002F 10);\n});\n\n\u002F\u002F Order of execution for a single options type:\n\u002F\u002F 1. All Configure\u003CT> calls (in registration order)\n\u002F\u002F 2. All PostConfigure\u003CT> calls (in registration order)\n\u002F\u002F Result: PostConfigure always wins — useful for library guarantees.\n\n\u002F\u002F PostConfigureAll — applies to all named instances:\nbuilder.Services.PostConfigureAll\u003CApiClientSettings>(settings =>\n{\n    \u002F\u002F Ensure every named ApiClientSettings has a trailing slash on BaseUrl:\n    if (!settings.BaseUrl.EndsWith('\u002F'))\n        settings.BaseUrl += '\u002F';\n});\n```\n\n**Rule of thumb:** Use `Configure\u003CT>` for setting values. Use `PostConfigure\u003CT>` in\nlibrary code to enforce invariants or caps that must hold regardless of what application\ncode configured.\n",{"id":221,"difficulty":43,"q":222,"a":223},"options-in-tests","How do you inject options in unit tests without a real configuration file?","Use **`Microsoft.Extensions.Options.Options.Create\u003CT>()`** to wrap an in-memory instance\nas `IOptions\u003CT>` — no `IConfiguration`, `IServiceCollection`, or `appsettings.json`\nneeded.\n\n```csharp\n\u002F\u002F System under test:\npublic class EmailService\n{\n    private readonly SmtpSettings _settings;\n    public EmailService(IOptions\u003CSmtpSettings> options)\n        => _settings = options.Value;\n\n    public string BuildSubject(string template)\n        => template.Replace(\"{domain}\", _settings.Domain);\n}\n\n\u002F\u002F Unit test — no container, no config file:\npublic class EmailServiceTests\n{\n    [Fact]\n    public void BuildSubject_ReplacesTokenWithDomain()\n    {\n        \u002F\u002F Arrange — wrap an in-memory instance:\n        var options = Options.Create(new SmtpSettings\n        {\n            Host   = \"smtp.test.com\",\n            Port   = 587,\n            Domain = \"test.com\"\n        });\n\n        var sut = new EmailService(options);\n\n        \u002F\u002F Act:\n        var result = sut.BuildSubject(\"Hello from {domain}\");\n\n        \u002F\u002F Assert:\n        Assert.Equal(\"Hello from test.com\", result);\n    }\n}\n\n\u002F\u002F For IOptionsSnapshot\u003CT> tests, use a mock or TestOptionsManager:\nvar snapshot = Substitute.For\u003CIOptionsSnapshot\u003CSmtpSettings>>();\nsnapshot.Value.Returns(new SmtpSettings { Host = \"smtp.test.com\" });\nsnapshot.Get(\"Stripe\").Returns(new SmtpSettings { Host = \"stripe.smtp.com\" });\n```\n\n**Rule of thumb:** Use `Options.Create(new T { ... })` in unit tests — it's simpler\nthan building a full DI container and keeps tests fast and deterministic.\n",{"id":225,"difficulty":35,"q":226,"a":227},"options-builder-pattern","How does the AddOptions\u003CT> fluent builder improve on Configure\u003CT>?","**`AddOptions\u003CT>()`** returns an `OptionsBuilder\u003CT>` that chains binding, validation,\nand post-configure steps fluently — cleaner than separate `Configure`, `AddSingleton`\n(for validators), and `PostConfigure` calls.\n\n```csharp\n\u002F\u002F Verbose equivalent (three separate calls):\nbuilder.Services.Configure\u003CJwtSettings>(builder.Configuration.GetSection(\"Jwt\"));\nbuilder.Services.AddSingleton\u003CIValidateOptions\u003CJwtSettings>, JwtSettingsValidator>();\nbuilder.Services.PostConfigure\u003CJwtSettings>(s => s.Issuer = s.Issuer.ToLower());\n\n\u002F\u002F Fluent equivalent with OptionsBuilder:\nbuilder.Services\n    .AddOptions\u003CJwtSettings>()\n    .BindConfiguration(\"Jwt\")\n    .Validate(s => s.Secret.Length >= 32,\n        \"Jwt:Secret must be at least 32 characters for HS256\")\n    .Validate(s => Uri.TryCreate(s.Issuer, UriKind.Absolute, out _),\n        \"Jwt:Issuer must be a valid absolute URI\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart()       \u002F\u002F ← key: throws at startup, not first request\n    .PostConfigure(s => s.Issuer = s.Issuer.TrimEnd('\u002F').ToLower());\n\n\u002F\u002F Full example with a realistic settings class:\npublic class JwtSettings\n{\n    [Required, MinLength(32)]\n    public string Secret { get; set; } = \"\";\n\n    [Required]\n    public string Issuer { get; set; } = \"\";\n\n    [Required]\n    public string Audience { get; set; } = \"\";\n\n    [Range(1, 1440)]\n    public int ExpiryMinutes { get; set; } = 60;\n}\n```\n\n**Rule of thumb:** Prefer `AddOptions\u003CT>().BindConfiguration().Validate*().ValidateOnStart()`\nover scattered `Configure` + `PostConfigure` + manual validator registrations.\nChaining keeps the intent visible in one place.\n",{"id":229,"difficulty":35,"q":230,"a":231},"options-reload","How does configuration reloading work with the options pattern?","ASP.NET Core's file-based configuration providers (like `appsettings.json`) support\n**hot reload** — they watch the file and re-read it on change. The options interfaces\ndiffer in how they expose reloaded values.\n\n```csharp\n\u002F\u002F Enable reloadOnChange (default in WebApplication.CreateBuilder):\nbuilder.Configuration.AddJsonFile(\"appsettings.json\",\n    optional: false, reloadOnChange: true);  \u002F\u002F watches the file\n\n\u002F\u002F IOptions\u003CT> — NEVER reloads; snapshot taken at startup:\npublic class StaticService\n{\n    public StaticService(IOptions\u003CAppSettings> options)\n    {\n        \u002F\u002F options.Value is frozen at startup — file changes ignored\n    }\n}\n\n\u002F\u002F IOptionsSnapshot\u003CT> — re-reads per HTTP request (reflects file changes):\npublic class PerRequestService\n{\n    private readonly AppSettings _settings;\n\n    public PerRequestService(IOptionsSnapshot\u003CAppSettings> options)\n    {\n        \u002F\u002F options.Value is fresh on every request — picks up file changes\n        _settings = options.Value;\n    }\n}\n\n\u002F\u002F IOptionsMonitor\u003CT> — registers a change callback:\npublic class LiveSettingsService\n{\n    private AppSettings _current;\n    private readonly IDisposable? _changeToken;\n\n    public LiveSettingsService(IOptionsMonitor\u003CAppSettings> monitor)\n    {\n        _current = monitor.CurrentValue;\n        _changeToken = monitor.OnChange(updated =>\n        {\n            _current = updated;\n            Console.WriteLine($\"Settings reloaded at {DateTime.UtcNow:O}\");\n        });\n    }\n\n    public void Dispose() => _changeToken?.Dispose();\n}\n```\n\n**Rule of thumb:** For feature flags and A\u002FB config that must update without restart,\nuse `IOptionsMonitor\u003CT>` in singletons. For values that are safe to cache per-request,\nuse `IOptionsSnapshot\u003CT>`. Never use `IOptions\u003CT>` for values that need live reloads.\n",{"id":233,"difficulty":43,"q":234,"a":235},"options-di-registration","What does builder.Services.Configure\u003CT> actually do under the hood?","`Configure\u003CT>` registers a named `IConfigureOptions\u003CT>` service. When `IOptions\u003CT>`\nis first accessed, the options framework runs every registered `IConfigureOptions\u003CT>` in\norder to build the final settings object.\n\n```csharp\n\u002F\u002F What Configure\u003CT>(section) actually does:\nbuilder.Services.Configure\u003CSmtpSettings>(\n    builder.Configuration.GetSection(\"Smtp\"));\n\n\u002F\u002F Internally equivalent to:\nbuilder.Services.AddSingleton\u003CIConfigureOptions\u003CSmtpSettings>>(\n    new ConfigureNamedOptions\u003CSmtpSettings>(\n        Options.DefaultName,           \u002F\u002F name = \"\" (the default name)\n        opts => builder.Configuration  \u002F\u002F action binds the section\n            .GetSection(\"Smtp\").Bind(opts)));\n\n\u002F\u002F Registering multiple Configure\u003CT> calls stacks the actions:\nbuilder.Services.Configure\u003CSmtpSettings>(s => s.Port = 587);    \u002F\u002F action 1\nbuilder.Services.Configure\u003CSmtpSettings>(s => s.UseTls = true); \u002F\u002F action 2\n\u002F\u002F Both run in order when IOptions\u003CSmtpSettings>.Value is first accessed.\n\n\u002F\u002F You can inspect what's registered:\nvar descriptors = builder.Services\n    .Where(d => d.ServiceType == typeof(IConfigureOptions\u003CSmtpSettings>))\n    .ToList();\n\u002F\u002F Lists all Configure\u003CSmtpSettings> registrations in order\n```\n\nUnderstanding this lets you reason about layering: each `Configure\u003CT>` call adds\nanother action that runs against the same object in sequence, so later calls can\noverride earlier ones.\n\n**Rule of thumb:** Because `Configure\u003CT>` is additive, library code should use\n`TryAdd`-based patterns or `PostConfigure` to avoid overwriting application settings.\nApplication code registers last and wins.\n",{"id":237,"difficulty":43,"q":238,"a":239},"options-vs-configuration","When should you inject IOptions\u003CT> vs IConfiguration directly?","**`IConfiguration`** is the raw key-value source. **`IOptions\u003CT>`** is the strongly\ntyped, validated, injectable wrapper. As a rule, only infrastructure\u002Fstartup code\nshould touch `IConfiguration` directly.\n\n```csharp\n\u002F\u002F Application service reading raw IConfiguration — fragile, untestable:\npublic class PaymentService\n{\n    public PaymentService(IConfiguration config)\n    {\n        \u002F\u002F Magic strings; no compile-time check; null if key is missing:\n        var apiKey = config[\"Stripe:ApiKey\"];\n        var timeout = int.Parse(config[\"Stripe:TimeoutMs\"] ?? \"5000\");\n    }\n}\n\n\u002F\u002F Application service using IOptions\u003CT> — typed, validated, testable:\npublic record StripeSettings(string ApiKey, int TimeoutMs = 5000);\n\npublic class PaymentService\n{\n    private readonly StripeSettings _stripe;\n\n    public PaymentService(IOptions\u003CStripeSettings> options)\n        => _stripe = options.Value; \u002F\u002F compile-time names; validated at startup\n\n    \u002F\u002F Test: Options.Create(new StripeSettings(\"test_key\"))\n}\n\n\u002F\u002F Legitimate IConfiguration use — startup wiring, extension methods:\nbuilder.Services.Configure\u003CStripeSettings>(\n    builder.Configuration.GetSection(\"Stripe\")); \u002F\u002F infrastructure, not app code\n\nbuilder.Services.AddSingleton\u003CIConnectionFactory>(sp =>\n{\n    var connStr = sp.GetRequiredService\u003CIConfiguration>()\n        .GetConnectionString(\"Default\");     \u002F\u002F acceptable in factory delegates\n    return new SqlConnectionFactory(connStr!);\n});\n```\n\n**Rule of thumb:** Inject `IOptions\u003CT>` in application services. Reserve\n`IConfiguration` for `Program.cs`, startup extensions, and factory delegates where\nyou're wiring infrastructure — not implementing business logic.\n",{"id":241,"difficulty":35,"q":242,"a":243},"options-environment-overrides","How do you provide environment-specific overrides for options in ASP.NET Core?","ASP.NET Core's configuration system layers sources in order — last one wins. The\nstandard `appsettings.{Environment}.json` override pattern and environment variables\nboth work automatically with the options pattern.\n\n```csharp\n\u002F\u002F appsettings.json (base defaults):\n\u002F\u002F { \"Email\": { \"Host\": \"smtp.example.com\", \"Port\": 587 } }\n\n\u002F\u002F appsettings.Development.json (developer overrides):\n\u002F\u002F { \"Email\": { \"Host\": \"localhost\", \"Port\": 1025 } }  \u002F\u002F MailHog\u002FPapercut\n\n\u002F\u002F appsettings.Production.json (production values):\n\u002F\u002F { \"Email\": { \"Host\": \"smtp.sendgrid.net\", \"Port\": 465 } }\n\n\u002F\u002F WebApplication.CreateBuilder wires these automatically in order:\n\u002F\u002F 1. appsettings.json\n\u002F\u002F 2. appsettings.{ASPNETCORE_ENVIRONMENT}.json  (overlays base)\n\u002F\u002F 3. User secrets (Development only)\n\u002F\u002F 4. Environment variables (overlay everything)\n\u002F\u002F 5. Command-line args (highest priority)\n\n\u002F\u002F Options registration is unchanged — same code for all environments:\nbuilder.Services.AddOptions\u003CEmailSettings>()\n    .BindConfiguration(\"Email\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart();\n\n\u002F\u002F Override a single property via environment variable — no code change needed:\n\u002F\u002F DOTNET_Email__Host=smtp.override.com  (double underscore = config key separator)\n\u002F\u002F ASPNETCORE_Email__Port=2525\n\n\u002F\u002F Override in tests using WebApplicationFactory:\nawait using var factory = new WebApplicationFactory\u003CProgram>()\n    .WithWebHostBuilder(host =>\n        host.UseSetting(\"Email:Host\", \"smtp.test.local\")\n            .UseSetting(\"Email:Port\", \"1025\"));\n\n\u002F\u002F Or via in-memory configuration:\nawait using var factory = new WebApplicationFactory\u003CProgram>()\n    .WithWebHostBuilder(host =>\n        host.ConfigureAppConfiguration(cfg =>\n            cfg.AddInMemoryCollection(new Dictionary\u003Cstring, string?>\n            {\n                [\"Email:Host\"] = \"smtp.test.local\",\n                [\"Email:Port\"] = \"1025\"\n            })));\n```\n\n**Rule of thumb:** Keep `appsettings.json` as the documented baseline with safe\ndefaults. Let `appsettings.{Environment}.json` and environment variables layer over it.\nNever hard-code environment checks in application code — the config system handles it.\n",{"id":245,"difficulty":35,"q":246,"a":247},"options-secrets","How do you handle secrets (API keys, connection strings) with the options pattern?","Secrets should never live in `appsettings.json` (committed to source control). The\noptions pattern is compatible with all three standard secret sources: User Secrets\n(development), environment variables (CI\u002FCD and containers), and Azure Key Vault\n(production).\n\n```csharp\npublic class StripeSettings\n{\n    \u002F\u002F Non-secret — safe in appsettings.json:\n    public string BaseUrl    { get; set; } = \"https:\u002F\u002Fapi.stripe.com\";\n    public int    TimeoutMs  { get; set; } = 5000;\n\n    \u002F\u002F Secret — must come from a secret store, NOT appsettings.json:\n    public string SecretKey  { get; set; } = \"\";\n    public string WebhookKey { get; set; } = \"\";\n}\n\n\u002F\u002F Development: User Secrets (stored outside the repo in %APPDATA%\u002FMicrosoft\u002FUserSecrets):\n\u002F\u002F dotnet user-secrets set \"Stripe:SecretKey\" \"sk_test_...\"\n\u002F\u002F dotnet user-secrets set \"Stripe:WebhookKey\" \"whsec_...\"\n\u002F\u002F WebApplication.CreateBuilder auto-loads user secrets in Development.\n\n\u002F\u002F Production\u002FCI: environment variables (double underscore as separator):\n\u002F\u002F Stripe__SecretKey=sk_live_...\n\u002F\u002F Stripe__WebhookKey=whsec_...\n\n\u002F\u002F Azure Key Vault (for production managed secrets):\nbuilder.Configuration.AddAzureKeyVault(\n    new Uri(\"https:\u002F\u002Fmyvault.vault.azure.net\u002F\"),\n    new DefaultAzureCredential());\n\u002F\u002F Key Vault secrets map: \"Stripe--SecretKey\" → config key \"Stripe:SecretKey\"\n\n\u002F\u002F Validation ensures secrets are present before the app starts:\nbuilder.Services.AddOptions\u003CStripeSettings>()\n    .BindConfiguration(\"Stripe\")\n    .Validate(s => !string.IsNullOrEmpty(s.SecretKey),\n        \"Stripe:SecretKey is required — set via user secrets or environment variable\")\n    .ValidateOnStart();\n```\n\n**Rule of thumb:** Keep secrets out of source control entirely. Use User Secrets for\ndevelopment, environment variables or a secrets manager for production. Options\nvalidation with `ValidateOnStart()` catches missing secrets at startup with a clear\nerror message.\n",{"id":249,"difficulty":35,"q":250,"a":251},"options-pattern-complex-types","How do you bind nested objects, collections, and enums in options classes?","The configuration binder handles nested objects, arrays, dictionaries, and enums\nautomatically — you just need the POCO structure to match the JSON shape.\n\n```csharp\n\u002F\u002F appsettings.json:\n\u002F\u002F {\n\u002F\u002F   \"Notification\": {\n\u002F\u002F     \"Email\": { \"Host\": \"smtp.example.com\", \"Port\": 587 },\n\u002F\u002F     \"Sms\":   { \"Provider\": \"Twilio\", \"From\": \"+15555550100\" },\n\u002F\u002F     \"EnabledChannels\": [\"Email\", \"Sms\"],\n\u002F\u002F     \"Templates\": {\n\u002F\u002F       \"Welcome\": \"Welcome to {AppName}!\",\n\u002F\u002F       \"Reset\":   \"Your reset code is {Code}.\"\n\u002F\u002F     },\n\u002F\u002F     \"RetryPolicy\": { \"MaxAttempts\": 3, \"BackoffStrategy\": \"Exponential\" }\n\u002F\u002F   }\n\u002F\u002F }\n\npublic enum BackoffStrategy { Linear, Exponential, Fixed }\n\npublic class RetryPolicySettings\n{\n    public int             MaxAttempts     { get; set; } = 3;\n    public BackoffStrategy BackoffStrategy { get; set; } = BackoffStrategy.Exponential;\n}\n\npublic class NotificationSettings\n{\n    \u002F\u002F Nested object — bound recursively:\n    public EmailConfig Email { get; set; } = new();\n    public SmsConfig   Sms   { get; set; } = new();\n\n    \u002F\u002F Array — bound from JSON array:\n    public List\u003Cstring> EnabledChannels { get; set; } = new();\n\n    \u002F\u002F Dictionary — bound from JSON object:\n    public Dictionary\u003Cstring, string> Templates { get; set; } = new();\n\n    \u002F\u002F Nested object with enum — enum bound by name (case-insensitive):\n    public RetryPolicySettings RetryPolicy { get; set; } = new();\n}\n\nbuilder.Services.AddOptions\u003CNotificationSettings>()\n    .BindConfiguration(\"Notification\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart();\n\n\u002F\u002F Usage:\npublic class NotificationService\n{\n    private readonly NotificationSettings _settings;\n\n    public NotificationService(IOptions\u003CNotificationSettings> opts)\n        => _settings = opts.Value;\n\n    public bool IsChannelEnabled(string channel)\n        => _settings.EnabledChannels.Contains(channel, StringComparer.OrdinalIgnoreCase);\n\n    public string GetTemplate(string name)\n        => _settings.Templates.TryGetValue(name, out var t) ? t : \"\";\n}\n```\n\n**Rule of thumb:** Match your POCO property names and nesting to the JSON structure —\nthe binder is case-insensitive and handles most types automatically. Use `init` setters\nfor immutability if you don't need post-binding mutation.\n",{"id":253,"difficulty":96,"q":254,"a":255},"options-change-token","How does IOptionsMonitor\u003CT> use change tokens to detect configuration reloads?","`IOptionsMonitor\u003CT>` tracks config changes via **`IChangeToken`** — the same\nabstraction that `IFileProvider` and `IConfiguration` use internally. Understanding\nthis mechanism helps when building custom configuration sources that support hot reload.\n\n```csharp\n\u002F\u002F IOptionsMonitor\u003CT> exposes the OnChange callback — backed by change tokens internally:\npublic class FeatureFlagMonitor : IDisposable\n{\n    private FeatureFlags _current;\n    private readonly IDisposable? _subscription;\n\n    public FeatureFlagMonitor(IOptionsMonitor\u003CFeatureFlags> monitor)\n    {\n        _current      = monitor.CurrentValue;\n        \u002F\u002F OnChange returns an IDisposable subscription — dispose to unsubscribe:\n        _subscription = monitor.OnChange((updated, name) =>\n        {\n            \u002F\u002F name is null for the default (unnamed) options instance:\n            if (name is null or \"\")\n            {\n                _current = updated;\n                \u002F\u002F Note: this callback may fire on a thread pool thread — protect shared\n                \u002F\u002F state if needed (Interlocked.Exchange, lock, etc.)\n            }\n        });\n    }\n\n    public bool IsEnabled(string flag)\n        => _current.Flags.TryGetValue(flag, out var v) && v;\n\n    public void Dispose() => _subscription?.Dispose(); \u002F\u002F unsubscribe on cleanup\n}\n\n\u002F\u002F Custom configuration source with change token support:\npublic class DatabaseConfigSource : IConfigurationSource\n{\n    public IConfigurationProvider Build(IConfigurationBuilder builder)\n        => new DatabaseConfigProvider();\n}\n\npublic class DatabaseConfigProvider : ConfigurationProvider, IDisposable\n{\n    private readonly Timer _timer;\n\n    public DatabaseConfigProvider()\n    {\n        \u002F\u002F Poll the database every 60 seconds and signal a change if values differ:\n        _timer = new Timer(_ => CheckForChanges(), null,\n            TimeSpan.Zero, TimeSpan.FromSeconds(60));\n    }\n\n    private void CheckForChanges()\n    {\n        Load(); \u002F\u002F re-read from DB\n        OnReload(); \u002F\u002F fires IChangeToken — triggers IOptionsMonitor callbacks\n    }\n\n    public override void Load() { \u002F* read key-values from DB into Data dict *\u002F }\n    public void Dispose() => _timer.Dispose();\n}\n```\n\n**Rule of thumb:** Always dispose the `IDisposable` returned by `OnChange` — a leaked\nsubscription holds a reference to your callback (and its closure) for the app lifetime.\nIn Singleton services, store the subscription and dispose it in `IDisposable.Dispose`.\n",{"description":32},"Options pattern interview questions — IOptions vs IOptionsSnapshot vs IOptionsMonitor, named options, validation, and binding from configuration.","dotnet\u002Fdependency-injection\u002Foptions-pattern","V7PTrdSyjF649323blV9pbg5ton8Im07Yu6rAlqIoDg",1782244097498]