Skip to content

Configuration settings

SAIF.NetCore.EnvironmentClient is the in-repository implementation of IEnvironmentClient: it does not consume Environment API keys of its own, it defines how every other library's GetSettingValue/GetSecureSetting/HasSetting call is resolved. This page documents both the bootstrap configuration this library reads to configure itself and the key-addressing contract it exposes to every consumer.

Configuration retrieval methods

Method Class / Interface Namespace Usage pattern
Environment.GetEnvironmentVariable("EnvironmentApiUrl") System.Environment System Field initializer of BaseEnvironmentClient.ServiceUrl, read once per client instance before the constructor body and independent of IConfiguration.
IConfiguration.GetSection(top).GetSection(key).Value IConfiguration Microsoft.Extensions.Configuration Imperative string read of AppSettings:* keys; missing → "". No binding to an options class.
IConfiguration.GetValue<T>(key, default) IConfiguration (ConfigurationBinder) Microsoft.Extensions.Configuration Typed read with a code default, wrapped in the internal ClientConfigBase.GetValue<T> helper, which composes EnvironmentClient:{SubSection}:{key}.
IConfiguration[key] (indexer) IConfiguration Microsoft.Extensions.Configuration Root-level string compare config["COREAPIENABLED"] != "1".
IHostEnvironment.ApplicationName / ContentRootPath IHostEnvironment Microsoft.Extensions.Hosting Host-supplied identity: application name used in every Environment API URL, and content root used for host inference.
Active Directory lookup System.DirectoryServices.AccountManagement (PrincipalContext, ComputerPrincipal, GroupPrincipal) System.DirectoryServices.AccountManagement Determines whether the machine is a member of Developer_Machine_Users; gates every developer-only configuration path.
File.Exists / File.ReadAllText + JsonConvert.DeserializeObject<ConfigSetting[]> System.IO.File, Newtonsoft System.IO, Newtonsoft.Json Developer override file (path from AppSettings:EnvApiDeveloperSettingsOverride); re-read on every non-secure value lookup.

IOptions<T>/Configure<T>/Bind, ConfigurationManager.AppSettings, and ApplicationSettingsBase are not used by this library*.

IConfiguration catalog

Key (Section:Key) Required Default when absent Type / accepted values Purpose Consuming class / member Source
AppSettings:ApplicationHost Bootstrap — required on developer machines; ignored on non-developer machines none ("") string; must equal a host listed in some Environment API track (stored lowercased) Forces the environment host (and therefore the track) on a developer machine; its presence also defines IsDeveloperMachine() = true. HostResolver.TryResolveHostFromAppConfig HostResolver.cs
AppSettings:EnvApiDeveloperOverrideAllowed Bootstrap — optional true (missing or empty); an unparsable value → false true/false (case-insensitive) When true, performs an AD lookup for Developer_Machine_Users on every startup; when false, the host and file overrides are ignored entirely and no AD call is made. HostResolver.IsConfigOverrideAllowed HostResolver.cs
AppSettings:EnvApiDeveloperSettingsOverride Bootstrap — optional (developer only) none → no override file file path (absolute or relative; relative resolves against the process current directory*) Path to the developer override JSON. Honored only if the file exists and IsDeveloperMachine(). BaseEnvironmentClient.ResolveDeveloperSettingsOverride BaseEnvironmentClient.cs
EnvironmentClient:GetHostsByTrack:RetryCount Optional 3 int; ≤ 0 → no attempt, EnvironmentClientException thrown immediately with an empty log* Number of attempts for the startup track fetch. GetHostsByTrackConfig.RetryCount ClientConfigs/GetHostsByTrackConfig.cs
EnvironmentClient:GetHostsByTrack:RetrySleepMs Optional 3000 int (milliseconds); linear back-off, sleep attempt × RetrySleepMs after each failure Back-off unit for the retry above; worst case with defaults is 3 s + 6 s + 9 s = 18 s plus HTTP timeouts*. GetHostsByTrackConfig.RetrySleepMs ClientConfigs/GetHostsByTrackConfig.cs
COREAPIENABLED (configuration root) Required for SecureEnvironmentClient on non-developer machines none → secure client construction fails unless IsDeveloperMachine() exactly "1" (string compare; "true", "yes", " 1" all fail) Server opt-in for secure (crypto) settings. SecureEnvironmentClient..ctor SecureEnvironmentClient.cs

Keys that appear in sample appsettings.json test fixtures but that this library does not read: AppSettings:ApplicationName (a no-op, despite inviting the false belief that it sets the Environment API application identity)*.

Environment variables catalog

Variable Read mechanism Scope Accepted values Default when unset Effect
EnvironmentApiUrl Environment.GetEnvironmentVariable("EnvironmentApiUrl") — field initializer, evaluated before the constructor body and before any IConfiguration read Process; cannot be supplied by appsettings.json, command-line args, user secrets, or prefixed env vars Absolute base URL of the Environment API none — MissingEnvironmentUrlVariableException at Setup(), and the singleton IEnvironmentClient cannot be constructed Environment API base URL for every request this client makes
COREAPIENABLED (typically set as ASPNETCORE_COREAPIENABLED) IConfiguration["COREAPIENABLED"] (indirect) Depends on host configuration providers: ASP.NET Core web hosts chain the ASPNETCORE_-prefixed host configuration into app configuration, so both prefixed and unprefixed names work; console/Windows-service hosts built with BuildSaifConsoleApp only map the unprefixed name* "1" only unset → SecureEnvironmentClient throws unless IsDeveloperMachine() Server-level opt-in that allows the secure (crypto) client to be constructed

Code comment discrepancy: SecureEnvironmentClient.cs describes the alternative gate condition as debugging with a VisualStudioDir environment variable; the live code does not read VisualStudioDir — the alternative is IsDeveloperMachine() (AD group + AppSettings:ApplicationHost). The comment is stale.

Bootstrap sequence (pre-DI → first lookup)

All of the following runs inside the BaseEnvironmentClient constructor, and because ServiceExtensions registers the client as a singleton, it runs once on first resolution of IEnvironmentClient and blocks that resolution.

Step Input consumed Failure mode
1 EnvironmentApiUrl (OS environment variable, read before the constructor body) —
2 IHostEnvironment.ApplicationName —
3 Validate EnvironmentApiUrl non-empty MissingEnvironmentUrlVariableException
4 HttpClient.SetClientInfo with the machine name as a placeholder host —
5 EnvironmentClient:GetHostsByTrack:RetryCount/RetrySleepMs After RetryCount failures: EnvironmentClientException with the accumulated log
6 GET /v2/tracks/{ApplicationName}/value Empty or failed → retried (step 5)
7 AppSettings:EnvApiDeveloperOverrideAllowed → AD Developer_Machine_Users → AppSettings:ApplicationHost → ContentRootPath → machine name AD/domain unavailability is unhandled*
8 Resolve the track whose host list contains the resolved host EnvironmentClientException("No environment track found for host! Host value: {ApplicationHost}")
9 AppSettings:EnvApiDeveloperSettingsOverride (file must exist and IsDeveloperMachine()) Silently ignored when not applicable
10 HttpClient.SetClientInfo with ApplicationHost/ApplicationName; TimeToWaitSeconds = 60 —
11 (SecureEnvironmentClient only) COREAPIENABLED == "1" or IsDeveloperMachine() EnvironmentClientException("This server is not enabled for CORE APIs."), evaluated after steps 1–10 have already called the Environment API

Environment API key-addressing contract

This library reads no Environment API keys of its own — it calls only structural endpoints (tracks, settings-by-section, lastRefreshed, secure setting). The table below documents the key-addressing rules it imposes on every consumer key.

Aspect Rule
Composed cache key "{section}.{key}"; the section is lowercased on both store and lookup, but the stored key portion keeps the casing returned by the Environment API — see "Case" below. Consumers' own catalogs record this as <section>:<key>.
Section omitted, GetSettingValue(key) Resolved in the track section: "{TrackName}.{key}".
Section omitted, HasSetting(key) Resolved as the bare key (no track prefix) — can disagree with the rule above for the same key.
Named section Loaded lazily from /v2/settings/{app}/{host}/{section}/value on first access and cached; a section returning zero settings is retried on next access.
Case BuildDictionaryKey lowercases the entire composed lookup key (section and requested setting key alike), but StoreSettingInDictionary only lowercases the section when storing — the setting-key portion is stored exactly as ConfigKey.Key was returned by the Environment API. Lookup is therefore case-insensitive for the section, but not guaranteed case-insensitive for the key: a request whose key casing differs from the API's returned casing can fail to match*.
Refresh At most every 60 s, GET /settings/lastRefreshed (an empty cache bypasses this throttle and always refreshes); a newer server timestamp clears the cache and reloads the root section (literal empty-string section, not the track section) plus every previously loaded section.
Secure key existence prerequisite A secure value is fetched only when a non-secure entry with the same section/key exists in the settings cache; otherwise "No crypto data found". Every secure key must also be published as a (placeholder) non-secure setting in the same section*.
Secure key scope Secure settings are addressed by track, non-secure by host.

Consumers of this contract in the repository: SAIF.NetCore.OIDC, SAIF.NetCore.AuthService, SAIF.NetCore.Logging, SAIF.NetCore.DBConnectionClient, SAIF.NetCore.Clients.UC4, SAIF.NetCore.Services.Messaging, and SAIF.NetCore.ServiceExtensions (including the secure read dbconnectionclient:connectionservicecreds). Their individual keys are cataloged in each library's own configuration page, not here.

Developer overrides

File / flag When applied What it overrides What it does not override
AppSettings:ApplicationHost EnvApiDeveloperOverrideAllowed not false and the computer account is a member of AD group Developer_Machine_Users The environment host, therefore the track for every non-secure and secure lookup; also flips IsDeveloperMachine() to true, which unlocks the file override below and bypasses COREAPIENABLED for SecureEnvironmentClient —
Developer settings override file (path from AppSettings:EnvApiDeveloperSettingsOverride, conventionally developersettings.json) File exists and IsDeveloperMachine(); re-read and re-parsed on every non-secure value lookup (parse errors are swallowed) Values returned by GetSettingValue/GetSettingValueAsync (string and <T>), matched case-insensitively on "{section}.{key}" (or bare key when section is empty); the file's track field is ignored HasSetting (an override-only key still reports false), GetSectionSettings, and all secure settings (which gate on HasSetting and go to the API); the cached Environment API values are untouched

File shape (ConfigSetting[], Newtonsoft, property names case-insensitive):

[
  { "ConfigKey": { "track": "<ignored>", "section": "<section>", "key": "<key>" },
    "ConfigValue": { "value": "<override value>" } }
]

Neither override is ever a production configuration source — both require AD group membership of the machine account. On a non-domain-joined or Linux host the AD call is expected to throw unless EnvApiDeveloperOverrideAllowed is false*.

Secure settings

This library reads no secure setting of its own; it implements secure retrieval for all consumers.

Aspect Base client Secure client
Availability Denied for the eight systemHashValue-string overloads, which reach RetrieveSecureSetting[Async] directly and unconditionally throw EnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings."); the eight SystemType overloads compute the hash via the (null-registered) ISystemHashService first and throw NullReferenceException before ever reaching that denial, so not all 16 overloads surface the same exception Allowed after the COREAPIENABLED == "1" or developer-machine construction gate
Authorization input (caller-supplied hash) systemHashValue string overloads Passed as ?systemHashValue={UrlEncoded} query parameter
Authorization input (SystemType overloads) — _systemHashService.Get(systemType), computed by the injected ISystemHashService
Endpoint — GET /v2/secureSetting/{app}/{track}[/{section}]/{key}/value?systemHashValue={UrlEncoded systemHashValue} (section is not URL-encoded)
Existence prerequisite — HasSetting(key, section) must be true in the non-secure cache
Caching — SecureSettingCache keyed "{section}.{key}" (case-sensitive); re-fetched when LastRefreshed (non-secure cache) is later than the item's fetch time; never cleared otherwise
Type conversion — Convert.ChangeType(result, typeof(T))

Transitive configuration

Source Requirement
SAIF.NetCore.ServiceContracts None — interfaces, enums, and POCOs only; no configuration reads
IHttpJsonClient implementation (supplied by the consumer, typically SAIF.NetCore.HttpClients) Any configuration the HTTP client needs (timeouts, auth headers) is owned by that package, not visible from this library**
ISystemHashService implementation (supplied by the consumer, typically SAIF.NetCore.SystemHash) Hash inputs are the SystemType and the registering assembly; no configuration keys observed from this side*
Hosting process EnvironmentApiUrl, and for the secure client COREAPIENABLED, must be provisioned on every server/container running any library depending on IEnvironmentClient

Configuration dependency risks

Risk Severity Note
Hard startup dependency on an OS environment variable outside IConfiguration High EnvironmentApiUrl cannot be supplied by appsettings.json, Key Vault, user secrets, or prefixed env vars; missing it cascades to every dependent SAIF NCore service*
Blocking network I/O in a DI constructor High First resolution of IEnvironmentClient performs HTTP calls with linear back-off (Thread.Sleep) — up to ~18 s plus timeouts with defaults*
AD lookup on every startup by default High EnvApiDeveloperOverrideAllowed defaults to true, so every host, including non-domain-joined/Linux containers, runs an AD lookup that is Windows-only and unhandled on failure*
COREAPIENABLED variable-name mismatch by host type Medium Console/Windows-service hosts need the unprefixed name; web-host guidance names the ASPNETCORE_-prefixed form*
COREAPIENABLED gate evaluated after network bootstrap Low A misconfigured server still performs the full Environment API handshake before failing
Silent empty-string semantics on missing settings Medium String overloads return ""; generic <T> overloads throw instead — consumers get different failure modes depending on the overload they chose
Base client registered with a null ISystemHashService Low SystemType secure overloads on BaseEnvironmentClient would throw NullReferenceException instead of the intended "Operation not allowed!"*
Developer override scope is narrower than it looks Low HasSetting, GetSectionSettings, and secure settings ignore the override file; only GetSettingValue* sees it
System hash travels in the query string Medium (security) May be captured by proxies or HTTP client logging*
Clock-skew-sensitive refresh Low Client local time vs. server lastRefreshed can miss or over-trigger cache reloads, which also governs secure-cache expiry*

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/ClientConfigBase.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/GetHostsByTrackConfig.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/Exceptions/MissingEnvironmentUrlVariableException.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient.Tests/TestData/appsettings.json