The Options Pattern
An `appsettings.json` section stores `MaxRetries: 3` as text, but a C# class wants it as a real `int`. One line pours those keys into typed properties, and how you read them back has three answers.
- ▸Bound values arrive typed, but a property with no setter, or a field, silently keeps its default and never throws.
- ▸The wrapper you inject, not the class, sets freshness:
IOptionsfreezes at startup,IOptionsSnapshotrebinds per request,IOptionsMonitorreads live. - ▸Injecting the scoped
IOptionsSnapshotinto a singleton is illegal;IOptionsMonitor, itself a singleton, is the singleton-safe live reader.
Reading config["RetryPolicy:MaxRetries"] gives you a string every time, so every consumer parses "3" into an int by hand and hopes the key is spelled right. The options pattern replaces that with a plain class:
public sealed class RetryPolicy { public int MaxRetries { get; set; } public TimeSpan Delay { get; set; } }
One registration line binds a named section onto it:
builder.Services.Configure<RetryPolicy>(
builder.Configuration.GetSection("RetryPolicy"));Configure<T> matches each key under the "RetryPolicy" section to a property of the same name and assigns it, converting the stored string to the property's type. Against this JSON:
{ "RetryPolicy": { "MaxRetries": 3, "Delay": "00:00:05" } }MaxRetries arrives as an int and Delay as a TimeSpan, parsed once at binding, not at every read. The section name is just a string argument: GetSection("Retry") would bind a section named Retry to the same class, so the class name and the section name are independent.