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* |
Related documentation¶
- Index
- Overview
- Architecture
- Dependencies
- Operations and processes
- Business rules
- Security and authorization
- API reference
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.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/ClientConfigBase.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/GetHostsByTrackConfig.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/Exceptions/MissingEnvironmentUrlVariableException.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient.Tests/TestData/appsettings.json