[{"data":1,"prerenderedAt":106},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern":3},{"page":4,"siblings":94,"blog":103},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":85,"related":86,"seo":87,"seoDescription":88,"stem":89,"subtopic":6,"topic":90,"topicSlug":91,"updated":92,"__hash__":93},"qa\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern.md","Options Pattern",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,3,"\u002Fdotnet\u002Fdependency-injection\u002Foptions-pattern",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,80],{"id":24,"difficulty":25,"q":26,"a":27},"options-what-is","easy","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":29,"difficulty":14,"q":30,"a":31},"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":33,"difficulty":25,"q":34,"a":35},"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":37,"difficulty":14,"q":38,"a":39},"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":41,"difficulty":14,"q":42,"a":43},"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":45,"difficulty":14,"q":46,"a":47},"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":49,"difficulty":25,"q":50,"a":51},"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":53,"difficulty":14,"q":54,"a":55},"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":57,"difficulty":14,"q":58,"a":59},"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":61,"difficulty":25,"q":62,"a":63},"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":65,"difficulty":25,"q":66,"a":67},"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":69,"difficulty":14,"q":70,"a":71},"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":73,"difficulty":14,"q":74,"a":75},"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":77,"difficulty":14,"q":78,"a":79},"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":81,"difficulty":82,"q":83,"a":84},"options-change-token","hard","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",15,null,{"description":11},"Options pattern interview questions — IOptions vs IOptionsSnapshot vs IOptionsMonitor, named options, validation, and binding from configuration.","dotnet\u002Fdependency-injection\u002Foptions-pattern","Dependency Injection","dependency-injection","2026-06-23","V7PTrdSyjF649323blV9pbg5ton8Im07Yu6rAlqIoDg",[95,99,102],{"subtopic":96,"path":97,"order":98},"DI Basics","\u002Fdotnet\u002Fdependency-injection\u002Fdi-basics",1,{"subtopic":100,"path":101,"order":12},"Service Lifetimes","\u002Fdotnet\u002Fdependency-injection\u002Fservice-lifetimes",{"subtopic":6,"path":21,"order":20},{"path":104,"title":105},"\u002Fblog\u002Fdotnet-options-pattern","The .NET Options Pattern: IOptions, IOptionsSnapshot, and IOptionsMonitor",1782244118546]