---
title: SAIF.NetCore.EnvironmentClient architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.EnvironmentClient
reference_type: package
---

# SAIF.NetCore.EnvironmentClient architecture

`SAIF.NetCore.EnvironmentClient` is the implementation-layer library that provides `BaseEnvironmentClient` and `SecureEnvironmentClient`, the concrete implementations of the `IEnvironmentClient` contract from `SAIF.NetCore.ServiceContracts`; it resolves which environment "track" the running application belongs to, then lazily downloads, caches, and refreshes named configuration settings — and, for the secure variant, crypto-protected settings — from a central Environment API over HTTP.

## Responsibilities
- Implement `IEnvironmentClient` and expose configuration-value accessors (`GetSettingValue`/`GetSettingValueAsync` with multiple overloads, `GetSectionSettings`, `HasSetting`) and secure-setting accessors (`GetSecureSetting`/`GetSecureSettingAsync`).
- Resolve the application's environment track and host identity at startup (`Setup`) via `GetHostsByTrack` and `IHostResolver.ResolveHost`, and support a developer-machine configuration override file.
- Lazily load and cache configuration sections and settings from the Environment API, with a time-based refresh check (`TestForRefreshSettings`, throttled to roughly 60 seconds).
- Gate access to secure (crypto) settings behind a system hash (`ISystemHashService`), implemented in `SecureEnvironmentClient`, which throws in the base class.
- Resolve the current host's Active Directory group membership (`HostResolver`) to decide trust/override behavior.

## Component boundaries
- `BaseEnvironmentClient`: the primary `IEnvironmentClient` implementation; owns configuration/section caching (`ConcurrentDictionary`, `HashSet` of loaded sections, `SemaphoreSlim`), host/track resolution, the developer override file, and all non-secure setting access. `RetrieveSecureSetting<T>`/`RetrieveSecureSettingAsync<T>` are `virtual` and throw "Operation not allowed!" by default*.
- `SecureEnvironmentClient`: inherits `BaseEnvironmentClient` and overrides the secure-setting retrieval methods, gated by a constructor check (`COREAPIENABLED=1` or developer machine) and backed by `SecureSettingCache`.
- `SecureSettingCache`: an in-memory `Get`/`GetAsync` cache keyed by setting key and last-refreshed timestamp, with a `ClearCache` method that is never invoked by this library*.
- `HostResolver` / `IHostResolver`: resolves the current host from a candidate list using Active Directory group membership (`System.DirectoryServices.AccountManagement`).
- `IEnvironmentClient`, `IHttpJsonClient`, `IHttpRequestResult`, `ISystemHashService` (ServiceContracts interfaces): consumed, not implemented beyond `IEnvironmentClient`; the HTTP transport and system-hash computation are supplied by whatever the host application registers (in the standard stack, `SAIF.NetCore.HttpClients` and `SAIF.NetCore.SystemHash`/`SAIF.NetCore.HashUtil` respectively)*.

## Interaction model
- A consuming application (typically via `SAIF.NetCore.ServiceExtensions`) constructs `BaseEnvironmentClient` or `SecureEnvironmentClient` with `IConfiguration`, `IHostEnvironment`, `IHttpJsonClient`, `IHostResolver`, and `ISystemHashService`. The constructor itself calls `Setup()` — setup is not a separate step the caller invokes; it runs synchronously during construction and resolves the application host and environment track against the Environment API before the constructor returns.
- Callers then invoke `GetSettingValue`/`GetSettingValueAsync` (optionally scoped to a section) or, on `SecureEnvironmentClient`, `GetSecureSetting`/`GetSecureSettingAsync`; both paths unconditionally run the refresh check (`CheckForUpdates`) first, then lazily load the target section from the Environment API if it has not already been loaded in the current cache generation.
- All outbound calls are issued through the injected `IHttpJsonClient` against the base URL supplied by the `EnvironmentApiUrl` OS environment variable; secure-setting calls additionally append a `systemHashValue` query parameter computed via `ISystemHashService.Get(SystemType)`.
- A local developer-settings override file (resolved by `HostResolver`'s Active Directory check) is consulted only **after** the non-secure setting path has already triggered the refresh-check/load sequence described above — it changes which value is returned once loading has happened, it does not let a developer machine skip the Environment API bootstrap or load/refresh network dependency. The override file is never consulted for secure settings, `HasSetting`, or `GetSectionSettings`.

## Architecture diagram
The following diagram shows how `BaseEnvironmentClient`/`SecureEnvironmentClient` sit between a consuming application and the remote Environment API, mediated by the ServiceContracts interfaces they depend on.

```mermaid
flowchart TD
    APP["Consuming application<br>(via SAIF.NetCore.ServiceExtensions)"] -->|constructs + calls| BEC["BaseEnvironmentClient"]
    BEC --> SEC["SecureEnvironmentClient<br>(inherits BaseEnvironmentClient)"]
    SEC --> CACHE["SecureSettingCache"]
    BEC -->|SetClientInfo / Get / GetAsync| HTTP["IHttpJsonClient<br>(ServiceContracts contract)"]
    SEC -->|"Get(SystemType)"| HASH["ISystemHashService<br>(ServiceContracts contract)"]
    HTTP -->|GET tracks/settings/secureSetting endpoints| API["Environment API<br>(external HTTP service)"]
    BEC -->|AD group lookup| RES["IHostResolver / HostResolver"]
```

## Sequence diagram
The following diagram shows construction-time setup followed by the primary request/response sequence for resolving a non-secure configuration setting, including the server-timestamp refresh probe, the empty-cache reload path, and section loading.

```mermaid
sequenceDiagram
    participant Consumer
    participant BaseEnvironmentClient
    participant IHttpJsonClient
    participant EnvironmentAPI
    Consumer->>BaseEnvironmentClient: new BaseEnvironmentClient(...)
    activate BaseEnvironmentClient
    BaseEnvironmentClient->>BaseEnvironmentClient: Setup() — resolve host/track, GET tracks endpoint
    BaseEnvironmentClient->>IHttpJsonClient: Get("/v2/tracks/{app}/value")
    IHttpJsonClient->>EnvironmentAPI: GET tracks
    EnvironmentAPI-->>IHttpJsonClient: wire response
    IHttpJsonClient-->>BaseEnvironmentClient: deserialized result
    deactivate BaseEnvironmentClient
    Consumer->>BaseEnvironmentClient: GetSettingValue(key, section)
    BaseEnvironmentClient->>BaseEnvironmentClient: CheckForUpdates (always runs first)
    alt Settings dictionary empty
        BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = true (no probe call)
    else at least 60s since last probe
        BaseEnvironmentClient->>IHttpJsonClient: Get("/settings/lastRefreshed")
        IHttpJsonClient->>EnvironmentAPI: GET lastRefreshed
        EnvironmentAPI-->>IHttpJsonClient: wire response
        IHttpJsonClient-->>BaseEnvironmentClient: server timestamp
        BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = server timestamp > LastRefreshed
    else probe not yet due
        BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = false
    end
    alt NeedsRefresh = true
        BaseEnvironmentClient->>BaseEnvironmentClient: LastRefreshed = now, Settings.Clear(), reload root + known sections
    end
    alt section not yet loaded this cache generation
        BaseEnvironmentClient->>IHttpJsonClient: Get("/v2/settings/{app}/{host}/{section}/value")
        IHttpJsonClient->>EnvironmentAPI: GET settings endpoint
        EnvironmentAPI-->>IHttpJsonClient: wire response
        IHttpJsonClient-->>BaseEnvironmentClient: deserialized ConfigurationSettings
        BaseEnvironmentClient->>BaseEnvironmentClient: AddOrUpdate each setting into global Settings dictionary
    end
    BaseEnvironmentClient->>BaseEnvironmentClient: Developer override lookup (checked after the load above, not instead of it)
    BaseEnvironmentClient-->>Consumer: setting value
```

## Design constraints
- No project references between libraries in this repository; the only cross-library dependency is the `SAIF.NetCore.ServiceContracts` NuGet package, consistent with the repository-wide layering in [`Architecture.md`](../../../Architecture.md).
- `SecureEnvironmentClient` gates secure-setting access behind `COREAPIENABLED=1` or developer-machine detection; the base class unconditionally throws for secure-setting retrieval, so secure access requires the derived type to be registered*.
- `LastRefreshed` is tracked using client local time but compared against the server's reported `lastRefreshed`, so clock skew between client and server could cause missed or extra refreshes*.
- The `Sections` `HashSet` is read outside its guarding semaphore in at least one path while also being mutated during reloads, which is a potential thread-safety gap*.
- Server-side Environment API semantics (hash validation, `lastRefreshed` meaning, response casing) are outside this repository and cannot be verified from source alone**.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)
- [SAIF.NetCore.ServiceContracts overview](../saif-netcore-servicecontracts/overview.md)

> **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/SecureSettingCache.cs`
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IEnvironmentClient.cs`, `ISystemHashService.cs`, `IHttpJsonClient.cs`, `IHttpRequestResult.cs`
- `Architecture.md`
