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

# Secure setting cache

| Field | Value |
| --- | --- |
| Owner | `SecureSettingCache.Get`/`GetAsync`/`ClearCache` (and private `GetSetting`/`FetchAndCache`/`GetFromCache` and their async equivalents) |
| Trigger | Step 3 of [Get secure setting](get-secure-setting.md) |
| Inputs | `key` (`"{section}.{configKey}"`), the caller's current `LastRefreshed` timestamp, a fetch delegate used on a cache miss or stale entry |
| Outputs | A cached or freshly fetched value, converted to `T` |

## Steps
1. `Get<T>(key, lastRefreshed, fetchFunction)` checks whether the internal `ConcurrentDictionary<string, SecureSettingCacheItem>` already contains `key`.
2. If **absent**, `FetchAndCache` invokes `fetchFunction()`. If the result is non-null, it creates a new `SecureSettingCacheItem { Key = key, Value = val, FetchTime = DateTime.Now }`, adds it via `TryAdd`, and returns `val`. If the fetch result is `null`, nothing is cached and `default(T)` is returned — a null/missing first fetch is not cached, so the next call will invoke the fetch delegate again.
3. If **present**, `GetFromCache` compares `item.FetchTime < lastRefreshed` (the caller's current `LastRefreshed`, which is updated by [Refresh check and cache reload](refresh-check-and-cache-reload.md) whenever a global reload happens) — this is a point-in-time comparison against the client's refresh timestamp, **not** an elapsed-seconds TTL check ([BR-EC-23](../business-rules.md#br-ec-23-secure-values-are-cached-per-section%2Fkey-and-re-fetched-only-after-a-refresh)). If the cached item predates the last refresh, `fetchFunction()` is invoked again and `item.Value`/`item.FetchTime` are overwritten in place (even with a `null` result from a re-fetch — an existing cached value can be replaced by `null` on a stale re-fetch, unlike the initial-fetch case). The (possibly just-updated) `item.Value` is then converted via `Convert.ChangeType(item.Value, typeof(T))` and returned (or `default(T)` if `item.Value` is `null`).
4. `ClearCache()` clears the entire dictionary. It is a public method on the class, but no caller in this repository invokes it.
5. The cache key does not include the system hash, so a hit is served regardless of which hash the current caller supplied — see [security and authorization, data-level finding DL-1](../security-authorization.md#data-level-authorization-findings).

## Process flow

```mermaid
flowchart TD
    A["Get key, lastRefreshed, fetchFunction"] --> B{Key present in cache?}
    B -- No --> C[Invoke fetchFunction]
    C -->|non-null result| D["Cache SecureSettingCacheItem<br>with FetchTime = now"]
    C -->|null result| E["Return default(T); nothing cached"]
    D --> F[Return value]
    B -- Yes --> G{"item.FetchTime < lastRefreshed?"}
    G -- No --> K{"item.Value == null?"}
    G -- Yes --> I[Invoke fetchFunction again]
    I --> J["Overwrite item.Value/FetchTime<br>(even if result is null)"]
    J --> K
    K -- Yes --> L["Return default(T)"]
    K -- No --> H["Return Convert.ChangeType(item.Value, T)"]
```

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