dotnet-csharp-configuration
Using Options pattern, user secrets, or feature flags. IOptions<T> and FeatureManagement.
What this skill does
# dotnet-csharp-configuration
Configuration patterns for .NET applications using Microsoft.Extensions.Configuration and Microsoft.Extensions.Options. Covers the Options pattern (`IOptions<T>`, `IOptionsMonitor<T>`, `IOptionsSnapshot<T>`), validation, user secrets, environment-based configuration, and feature flags with `Microsoft.FeatureManagement`.
Cross-references: [skill:dotnet-csharp-dependency-injection] for service registration patterns, [skill:dotnet-csharp-coding-standards] for naming conventions.
---
## Configuration Sources and Precedence
Default configuration sources in `WebApplication.CreateBuilder` (last wins):
1. `appsettings.json`
2. `appsettings.{Environment}.json`
3. User secrets (Development only)
4. Environment variables
5. Command-line arguments
```csharp
var builder = WebApplication.CreateBuilder(args);
// Sources above are loaded automatically. Add custom sources:
builder.Configuration.AddJsonFile("features.json", optional: true, reloadOnChange: true);
```
---
## Options Pattern
Bind configuration sections to strongly typed classes and inject them via DI.
### Defining Options Classes
```csharp
public sealed class SmtpOptions
{
public const string SectionName = "Smtp";
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public string FromAddress { get; set; } = "";
public bool UseSsl { get; set; } = true;
}
```
> Options classes use `{ get; set; }` (not `init`) because the configuration binder and `PostConfigure` need to mutate properties. Use `[Required]` via data annotations for mandatory fields instead.
### Registration
```csharp
builder.Services
.AddOptions<SmtpOptions>()
.BindConfiguration(SmtpOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
```
### `appsettings.json`
```json
{
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"FromAddress": "[email protected]",
"UseSsl": true
}
}
```
---
## Options Interfaces
| Interface | Lifetime | Reload Behavior | Use Case |
|-----------|----------|-----------------|----------|
| `IOptions<T>` | Singleton | Never reloads after startup | Static config, most services |
| `IOptionsSnapshot<T>` | Scoped | Reloads per request/scope | Per-request config in ASP.NET |
| `IOptionsMonitor<T>` | Singleton | Live reload + change notification | Singletons, background services |
### Injection Examples
```csharp
// Static -- most common, singleton-safe
public sealed class EmailService(IOptions<SmtpOptions> options)
{
private readonly SmtpOptions _smtp = options.Value;
public Task SendAsync(string to, string subject, string body,
CancellationToken ct = default)
{
// Use _smtp.Host, _smtp.Port, etc.
return Task.CompletedTask;
}
}
// Live reload in singletons -- monitors config file changes
public sealed class FeatureService(IOptionsMonitor<FeatureOptions> monitor)
{
public bool IsEnabled(string feature)
=> monitor.CurrentValue.EnabledFeatures.Contains(feature);
}
// Per-request in scoped services -- reads latest config each request
public sealed class PricingService(IOptionsSnapshot<PricingOptions> snapshot)
{
public decimal GetMarkup() => snapshot.Value.MarkupPercent;
}
```
### Change Notifications with `IOptionsMonitor<T>`
```csharp
public sealed class CacheService : IDisposable
{
private readonly IDisposable? _changeListener;
private CacheOptions _current;
public CacheService(IOptionsMonitor<CacheOptions> monitor)
{
_current = monitor.CurrentValue;
_changeListener = monitor.OnChange(updated =>
{
_current = updated;
// React to config change -- flush cache, resize pool, etc.
});
}
public void Dispose() => _changeListener?.Dispose();
}
```
---
## Options Validation
### Data Annotations
```csharp
using System.ComponentModel.DataAnnotations;
public sealed class SmtpOptions
{
public const string SectionName = "Smtp";
[Required, MinLength(1)]
public string Host { get; set; } = "";
[Range(1, 65535)]
public int Port { get; set; } = 587;
[Required, EmailAddress]
public string FromAddress { get; set; } = "";
}
builder.Services
.AddOptions<SmtpOptions>()
.BindConfiguration(SmtpOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart(); // Fail fast at startup, not on first use
```
### `IValidateOptions<T>` (Complex Validation)
Use when validation logic requires cross-property checks or external dependencies.
```csharp
public sealed class SmtpOptionsValidator : IValidateOptions<SmtpOptions>
{
public ValidateOptionsResult Validate(string? name, SmtpOptions options)
{
var failures = new List<string>();
if (options.UseSsl && options.Port == 25)
{
failures.Add("Port 25 does not support SSL. Use 465 or 587.");
}
if (string.IsNullOrWhiteSpace(options.Host))
{
failures.Add("SMTP host is required.");
}
return failures.Count > 0
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}
// Register the validator
builder.Services.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>();
```
### `ValidateOnStart` (Fail Fast)
Always use `.ValidateOnStart()` to surface configuration errors at startup instead of at first resolution. Without it, invalid config only throws when `IOptions<T>.Value` is first accessed.
---
## User Secrets (Development)
Store sensitive values outside source control during development.
```bash
# Initialize (once per project)
dotnet user-secrets init
# Set values
dotnet user-secrets set "Smtp:Host" "smtp.example.com"
dotnet user-secrets set "ConnectionStrings:Default" "Server=..."
# List all secrets
dotnet user-secrets list
# Clear all
dotnet user-secrets clear
```
User secrets are stored in `~/.microsoft/usersecrets/<UserSecretsId>/secrets.json` and override `appsettings.json` values in Development.
**Key rules:**
- Never use user secrets in production -- use environment variables, Azure Key Vault, or other vault providers
- User secrets are loaded automatically when `ASPNETCORE_ENVIRONMENT=Development`
- For non-web hosts, explicitly add: `builder.Configuration.AddUserSecrets<Program>()`
---
## Environment-Based Configuration
### Environment Variables
```csharp
// Hierarchical keys use __ (double underscore) as separator
// Environment variable: Smtp__Host=smtp.prod.com
// Maps to: configuration["Smtp:Host"]
```
### Per-Environment Files
```
appsettings.json # Base (all environments)
appsettings.Development.json # Overrides for dev
appsettings.Staging.json # Overrides for staging
appsettings.Production.json # Overrides for prod
```
```csharp
// Set environment via ASPNETCORE_ENVIRONMENT or DOTNET_ENVIRONMENT
// Defaults to "Production" if not set
var env = builder.Environment.EnvironmentName; // "Development", "Staging", "Production"
```
### Conditional Service Registration
```csharp
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IEmailSender, ConsoleEmailSender>();
}
else
{
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
}
```
---
## Feature Flags with Microsoft.FeatureManagement
`Microsoft.FeatureManagement.AspNetCore` provides structured feature flag support with filters, targeting, and gradual rollout.
### Setup
```bash
dotnet add package Microsoft.FeatureManagement.AspNetCore
```
```csharp
builder.Services.AddFeatureManagement();
```
### Configuration
```json
{
"FeatureManagement": {
"NewDashboard": true,
"BetaSearch": {
"EnabledFor": [
{
"Name": "Percentage",
"Parameters": { "Value": 50 }
}
]
},
"DarkMode": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": {
"Audience": {
"Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.