---
title: Get setting value
summary: OP-02 operation detail page.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.EnvironmentClient
reference_type: package
---

# Get setting value

| Field | Value |
| --- | --- |
| Owner | `GetSettingValue`/`GetSettingValueAsync` (string overloads) and `GetSettingValue<T>`/`GetSettingValueAsync<T>` (generic overloads), both with and without a `section` parameter |
| Trigger | Consumer call |
| Inputs | `configKey`, optional `section` |
| Outputs | The resolved string value (`""` when missing), or the typed value `T` (exception when missing) |

## Steps
1. Call `GetConfigSetting`/`GetConfigSettingAsync`. If `section` is omitted or blank, the section actually used is `TrackName` (the application's resolved environment track), not an empty/root section ([BR-EC-11](../business-rules.md#br-ec-11-a-section-less-setting-request-resolves-in-the-application's-track-section)).
2. Build the dictionary key via `BuildDictionaryKey(configKey, sectionToUse)` — lowercases `"{section}.{configKey}"` (or just `configKey` when the section is blank) ([BR-EC-12](../business-rules.md#br-ec-12-setting-keys-and-section-names-are-lowercased-on-lookup)).
3. Call `LoadSection`/`LoadSectionAsync` for `sectionToUse`.
   - 3.1. This always runs the refresh check first: an empty global cache (or any call once the server's `lastRefreshed` timestamp has advanced, probed at most every 60 seconds) clears and reloads the whole cache — `LastRefreshed` is set **before** that reload runs, not after it completes, so a failure partway through is not guaranteed to be retried on the very next call; see [Load section](load-section.md) step 1.
   - 3.2. If the (lowercased) `sectionToUse` key is already recorded in the `Sections` cache, `LoadSection`/`LoadSectionAsync` returns immediately with no network call; otherwise it acquires `SectionsSemaphore`, **re-checks** `Sections` under the lock (so a concurrent caller that just finished loading the same section is not re-fetched), and only then calls the Environment API. A successful fetch with 1+ settings adds the section to `Sections`; a successful fetch with **zero** settings is removed from `Sections` if present and retried on the very next call rather than being stuck; a non-success HTTP result or a malformed response throws `EnvironmentClientException` out of this call. This loaded-state guard applies only to a **nonempty** `sectionToUse`; since here it is always `TrackName` or a caller-supplied section (never the literal-empty-string root), it is reached normally ([BR-EC-13](../business-rules.md#br-ec-13-each-section-is-loaded-from-the-environment-api-at-most-once-per-cache-generation)).
   - > **Origin**: Steps 3.1–3.2 are implemented in [Load section](load-section.md) and [Refresh check and cache reload](refresh-check-and-cache-reload.md), and executed on behalf of this operation.
4. Look up the dictionary key via `GetSettingFromDictionary`.
   - 4.1. A developer override file configured for this process is re-read and parsed on every call; if it contains an entry whose formatted key (`"{section}.{key}"`, or bare `key` when section is empty) matches (case-insensitive), that entry's value is returned immediately, bypassing the Environment API-backed dictionary entirely. Parse errors on the override file are silently swallowed. The override lookup runs **after** step 3 has already triggered the network-backed load — an override does not avoid the bootstrap/refresh network dependency, it only changes which value is returned once loading has happened ([BR-EC-09](../business-rules.md#br-ec-09-a-developer-override-file-is-honored-only-when-it-exists-and-the-machine-is-a-developer-machine)).
   - > **Origin**: Step 4.1 is implemented in [Developer override lookup](developer-override-lookup.md) and executed on behalf of this operation.
5. If no override matched, look up `configKey` in the global `Settings` dictionary using the composed key. If absent, `GetSettingFromDictionary` returns an empty `ConfigSetting` (both `ConfigKey` and `ConfigValue` are `null`) rather than throwing.
6. For the **string** overloads (`GetSettingValue`/`GetSettingValueAsync`), the result is `setting.ConfigValue?.Value ?? ""` — a missing setting silently returns an empty string, never an exception ([BR-EC-18](../business-rules.md#br-ec-18-a-missing-non-secure-setting-returns-an-empty-string-from-string-overloads)).
7. For the **generic** overloads (`GetSettingValue<T>`/`GetSettingValueAsync<T>`), `ThrowExceptionIfNullOnGeneric` is called first. If `ConfigValue` is `null` (setting missing), it throws `EnvironmentClientException("Environment setting does not exist.  It must exist when calling the generic implementation of {methodName}.")` regardless of `T` — this guard is not itself generic, it only checks for a `null` `ConfigValue` ([BR-EC-19](../business-rules.md#br-ec-19-a-missing-setting-requested-through-a-generic-overload-is-an-error)). If present, `ConfigSettingExtensions.GetValue<T>()` converts the stored string via `Convert.ChangeType(setting.ConfigValue.Value, typeof(T))`; a conversion failure propagates `Convert.ChangeType`'s exception (for example `InvalidCastException` or `FormatException`). There is no supplied/injectable `TypeConverter` parameter on any overload.

## Process flow

The diagram below shows the developer-override, present-key, and absent-key paths converging on a single `setting.ConfigValue == null` guard before any string or generic handling runs — an override entry or a dictionary hit can itself carry a `null` `ConfigValue`, so key presence alone does not guarantee a usable value.

```mermaid
flowchart TD
    A["GetSettingValue(configKey, section?)"] --> B["sectionToUse = section or TrackName"]
    B --> C["BuildDictionaryKey:<br>lowercase section.configKey"]
    C --> D["Load section(sectionToUse)"]
    D --> E["Developer override lookup"]
    E -->|override found| F["setting = override entry"]
    E -->|no override| G{"Key in global<br>Settings dictionary?"}
    G -- Yes --> H["setting = Settings[key]"]
    G -- No --> I["setting = new ConfigSetting()<br>(ConfigValue is null)"]
    F --> J{"setting.ConfigValue<br>== null?"}
    H --> J
    I --> J
    J -- Yes --> K{"Generic overload?"}
    K -- Yes --> L["EnvironmentClientException:<br>setting does not exist"]
    K -- No --> M["Return setting.ConfigValue?.Value<br>?? #quot;#quot; (empty string)"]
    J -- No --> N{"Generic overload?"}
    N -- Yes --> O["Convert.ChangeType(ConfigValue.Value, T)"]
    N -- No --> P["Return setting.ConfigValue.Value<br>?? #quot;#quot; (empty string)"]
```

## Sources
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/Extensions/ConfigSettingExtensions.cs`
