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

# Has setting

| Field | Value |
| --- | --- |
| Owner | `HasSetting` |
| Trigger | Consumer call; also step 4 of [Get secure setting](get-secure-setting.md) |
| Inputs | `configKey`, optional `section` (default `""`) |
| Outputs | `true`/`false` |

## Steps
1. Build the dictionary key via `BuildDictionaryKey(configKey, section)` using the **literal** `section` argument as passed — unlike [Get setting value](get-setting-value.md)/`GetConfigSetting`, `HasSetting` does **not** substitute `TrackName` when `section` is blank. Calling `HasSetting(configKey)` with no section therefore checks the bare `configKey.ToLower()` key, while `GetSettingValue(configKey)` with no section resolves against `"{TrackName}.{configKey}"` — the two "no section" defaults are not equivalent ([BR-EC-12](../business-rules.md#br-ec-12-setting-keys-and-section-names-are-lowercased-on-lookup)).
2. Call `LoadSection` for the same literal `section` value.
   - 2.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.
   - 2.2. If the (lowercased) section key is already recorded in the `Sections` cache, `LoadSection` 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 treated as not-loaded and is removed from `Sections` if present, so it is 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 (and out of `HasSetting` itself) instead of returning `false`. Only a **nonempty** `section` is ever marked loaded this way; if the caller omitted `section` (so it is the literal empty-string root), this guard never short-circuits and the root is fetched again on every call that reaches it ([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 2.1–2.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.
3. Return `CheckForSetting(settingKey)`: `true` if the global `Settings` dictionary is non-empty **and** contains the composed key, `false` otherwise ([BR-EC-28](../business-rules.md#br-ec-28-hassetting-reports-presence-in-the-loaded-cache-for-the-given-section)).
4. This check never consults the developer override file (see [Developer override lookup](developer-override-lookup.md)) or the secure-setting API; it only reflects the non-secure settings most recently loaded from the Environment API.

## Process flow

```mermaid
flowchart TD
    A["HasSetting configKey, section=''"] --> B["BuildDictionaryKey(configKey, section)<br>— literal section, no TrackName substitution"]
    B --> C[Load section for literal section value]
    C --> D{"Settings non-empty and<br>contains composed key?"}
    D -- Yes --> E[Return true]
    D -- No --> F[Return false]
```

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