Core Concepts

Configuration managers

JsonConfigManager and EnvVarConfigManager are the two configuration manager types. Either supports any number of independently configured named instances, enabling configuration grouping by characteristics like scope, mutability, or sensitivity, for example one instance for plain settings and another for encrypted secrets:

var settings = JsonConfigManager.BuildInstance("settings")
    .Register<AppSettings>("app-settings.json")
    .Build();

var secrets = JsonConfigManager.BuildInstance("secrets")
    .ApplyEncryptionKey(() => "at-least-twelve-characters")
    .Register<ConnectionStrings>("connection-strings.aes", encrypted: true)
    .Build();

Each instance is retrieved later by its own name: GetInstance("settings"), GetInstance("secrets"). A name is a lookup key into the manager’s static registry, nothing more; unique only within a manager type, not across both. JsonConfigManager additionally scopes each instance to a single flat directory, another natural grouping boundary; see JSON Configuration. EnvVarConfigManager registrations resolve directly against the process environment by name.

Builder pattern

Both manager types share one build sequence:

  1. TManager.BuildInstance(name) returns a fluent builder, good for exactly one Build() call.
  2. Chain configuration and registration calls.
  3. .Build() validates everything registered and returns the live instance, or throws.
  4. TManager.GetInstance(name) retrieves a previously built instance from anywhere else in the process.
var configMgr = JsonConfigManager.BuildInstance("default")
    .Register<AppSettings>("app-settings.json")
    .Build();

// elsewhere in the process
var same = JsonConfigManager.GetInstance("default");

Build once at startup, before the instance is retrieved concurrently elsewhere. GetInstance throws ArgumentException if no instance was ever built under that name.

Validation on startup

Every registered type or variable is validated once, at Build() time, not lazily on first access. Any failure, a registered file missing, a registered environment variable unset, a validator failing, a duplicate registration, throws immediately at process start rather than surfacing minutes or hours later when some rarely-touched config value is finally read for the first time. The instance is also rolled back out of the static registry before the exception propagates, so no partially-validated instance is ever retrievable via GetInstance. See Validation for how validator failures aggregate into a single exception.

Encryption

Registrations needing encryption at rest, JSON or environment variable, opt in with encrypted: true, backed by authenticated AES-256-GCM. See Encryption for setup, parameters, and failure behavior.

Explicit by design

No merge step, no override precedence, no provider chain to trace. Each registration has exactly one source: Register<T>("file.json") reads that file; a registered environment variable reads that variable.

Concurrency

Once Build() returns successfully, every operation on that instance (Get<T>, Put<T>, ClearCached, ClearAllCached on JsonConfigManager; Get, IsEncrypted on EnvVarConfigManager) is synchronized through an internal lock: a built instance is safe to share and call concurrently across threads, no external locking required.

Neither manager coordinates across processes: no lock file, no shared-memory handshake. Put<T>’s optimistic concurrency check (see JSON Configuration) detects a change this instance has already observed going stale; it is not a substitute for process-level coordination on the underlying file.

Next steps