---
title: SAIF.NetCore.ServiceExtensions dependencies
summary: Internal and external dependencies for this library.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.ServiceExtensions
reference_type: package
---

# SAIF.NetCore.ServiceExtensions dependencies

`SAIF.NetCore.ServiceExtensions` directly references ten other same-repo SAIF packages as dependency-injection composition-root wiring, carries eight direct third-party packages (three ASP.NET Core/DI packages plus five OpenTelemetry packages), and makes no outbound HTTP call of its own code against an external service — but it does reach EnvironmentClient's remote Environment API through calls it makes directly into EnvironmentClient's own registered implementation (see the EnvironmentClient row and [API dependencies](#api-dependencies) below), so "resolves/registers types" is not true of every internal reference.

## Internal dependencies

| Dependency | Reference Type | Usage |
| --- | --- | --- |
| [SAIF.NetCore.ServiceContracts](../saif-netcore-servicecontracts/overview.md) | NuGet Package (same-repo) | Foundation interfaces/POCOs/exceptions used throughout the composition surface (`IErrorHandleable`, `SAIFException`, `SystemType`, `ShutdownExemptExceptionType`, and others). |
| [SAIF.NetCore.AuthService](../saif-netcore-authservice/overview.md) | NuGet Package (same-repo) | `RegisterEdgeAPIAuthServices`/`RegisterCoreAPIAuthServices` register AuthService's JWT/dual-authentication, identity, and SecurityApi authorization types (`ConfigureJwtBearerOptions`, `AddTokenIdentityService`, `AddIdentityAccessor`, `AddDualAuthentication`, `AddSecurityApiAuthorizeAttribute`) — DI composition-root wiring, not an outbound call made by this library. |
| [SAIF.NetCore.DBConnectionClient](../saif-netcore-dbconnectionclient/overview.md) | NuGet Package (same-repo) | `RegisterDBConnectionClient` constructs and registers `ConnectionClient`/`IDBConnectionClient` — composition-root wiring; `ConnectionClient`'s own outbound HTTP calls happen later, through the already-registered `IHttpJsonClient`, not during registration. |
| [SAIF.NetCore.EnvironmentClient](../saif-netcore-environmentclient/overview.md) | NuGet Package (same-repo) | `RegisterEnvironmentClient`/`RegisterSecureEnvironmentClient` register `BaseEnvironmentClient`/`SecureEnvironmentClient` as `IEnvironmentClient` (DI composition-root wiring). This library also *reaches* that registered `IEnvironmentClient` at runtime, not just at registration time: the `RegisterDBConnectionClient` factory calls `IEnvironmentClient.GetSecureSetting(key)` the first time `IDBConnectionClient` is resolved, and `AspNetSessionIdMiddleware` calls `IEnvironmentClient.GetSettingValueAsync(key)` on every request where the session cookie is absent. Both calls can reach EnvironmentClient's remote Environment API over HTTP on a cache miss — this is a real, reachable remote dependency, not local-provider-only configuration reads. |
| [SAIF.NetCore.ErrorHandler](../saif-netcore-errorhandler/overview.md) | NuGet Package (same-repo) | `RegisterErrorHandlerService` (also called by `RegisterFactoryActivatedMiddleware`) registers `ErrorHandler` as a scoped `IErrorHandleable`, consumed by this library's own `DynamicErrorHandler` middleware; `RegisterForWindowsService<T>` separately registers `WindowsServiceErrorHandler` as a singleton `IWindowsServiceErrorHandler` and `SaifWindowsServiceLog` as a singleton `ISaifWindowsServiceLogService` — distinct registrations, not both performed by the same method. |
| [SAIF.NetCore.HttpClients](../saif-netcore-httpclients/overview.md) | NuGet Package (same-repo) | Registers `HttpJsonClient` as `IHttpJsonClient` (ServiceContracts) for use by composed services such as `ConnectionClient`. |
| [SAIF.NetCore.Logging](../saif-netcore-logging/overview.md) | NuGet Package (same-repo) | `RegisterLoggingService` registers `SaifLog` as a singleton `ISaifLogService`; `ConfigureSaifLogger` separately calls `.Configure(apiUrl, applicationData)` on the already-registered `ISaifLogService` instance (it registers nothing itself). `RegisterForWindowsService<T>` registers `SaifWindowsServiceLog` as `ISaifWindowsServiceLogService` instead, and `IHostBuilderExtensions`'s console-host builders register `ConsoleLogService` as `IConsoleLogService` — each logger variant is wired by a different method for a different host shape, not interchangeably by `RegisterLoggingService`/`ConfigureSaifLogger` alone. |
| [SAIF.NetCore.OIDC](../saif-netcore-oidc/overview.md) | NuGet Package (same-repo) | `RegisterOidcServices`/`AddWebAuthentication` register OIDC's own middleware and services (`JwtClaimMiddleware`, `RefreshJwtMiddleware`, `OidcSettings`, `RoleClaimTransformer`, `LoginRedirectService`, cookie/OIDC options configurators) — composition-root wiring; the actual OIDC/token-exchange network calls happen inside OIDC's own implementation against the external Okta provider. |
| [SAIF.NetCore.PingableService](../saif-netcore-pingableservice/overview.md) | NuGet Package (same-repo) | `WithPingableController()` registers the health/ping endpoint contributed by this package. |
| [SAIF.NetCore.SystemHash](../saif-netcore-systemhash/overview.md) | NuGet Package (same-repo) | `RegisterSecureEnvironmentClient` adds a singleton `IEnvironmentClient` descriptor (type-based, `SecureEnvironmentClient`) and a *separate* singleton `ISystemHashService` factory, `() => new SystemHashService(Assembly.GetAssembly(type))` — this only selects and stores the caller-supplied assembly for later `Get` calls; constructing `SystemHashService` performs **no hash computation**, so no credential/hash is computed or cached at registration time. Each subsequent resolution-time call to `Get(SystemType)` computes and salts a hash freshly (see [Compute salted system hash](../saif-netcore-systemhash/operations/compute-salted-system-hash.md)). This is distinct from `RegisterDBConnectionClient`, whose own `IDBConnectionClient` factory explicitly constructs **a separate, unregistered** `SystemHashService` and calls `Get(SystemType.WebApp)` on it directly — a hash computation that *does* happen, once, the first time the DB connection client singleton is resolved (see [Register DB connection client](operations/register-db-connection-client.md) step 3). This library's direct reference to `SystemHash` also makes `HashUtil` transitively available to consumers, since `SystemHash` itself directly references `HashUtil` (see [Dependency risks and notes](#dependency-risks-and-notes) below). |

## External dependencies

| Dependency | Version | Usage |
| --- | --- | --- |
| `Microsoft.AspNetCore.Authentication.Negotiate` | `8.0.0` (net8.0) / `10.0.12` (net10.0) | Supplies the IIS/Negotiate Windows authentication scheme used by `AppStartup` and `AddDualAuthentication`-based Core/internal hosts. |
| `Microsoft.AspNetCore.Mvc.NewtonsoftJson` | `8.0.0` (net8.0) / `10.0.12` (net10.0) | `.AddNewtonsoftJson()` is called from `AppStartup`, `OidcRazorStartup`, and `BaseApiStartup.ConfigureMvcBuilder` when `EnableNewtonsoftJsonSerializer` is set, enabling the optional Newtonsoft JSON serializer for MVC. |
| `Microsoft.Extensions.DependencyInjection` | `8.0.0` (both net8.0 and net10.0) | Supplies the `IServiceCollection` extension-method surface underlying all registration code in this library; not bumped to a 10.x version on the `net10.0` target framework*. |
| `OpenTelemetry.Exporter.OpenTelemetryProtocol` | `1.9.0` | `UseOtlpExporter()` in `OpenTelemetryExtensions.ConfigureOpenTelemetry` configures the OTLP exporter. |
| `OpenTelemetry.Extensions.Hosting` | `1.9.0` | `AddOpenTelemetry()` host integration entry point used by `ConfigureOpenTelemetry`. |
| `OpenTelemetry.Instrumentation.AspNetCore` | `1.9.0` | `AddAspNetCoreInstrumentation()` for ASP.NET Core request metrics and tracing. |
| `OpenTelemetry.Instrumentation.Http` | `1.9.0` | `AddHttpClientInstrumentation()` for outbound `HttpClient` metrics and tracing. |
| `OpenTelemetry.Instrumentation.Runtime` | `1.9.0` | `AddRuntimeInstrumentation()` for .NET runtime metrics. |

## Transitive dependencies

Dependencies in this section are inherited from dependency framework libraries. They are not directly referenced by this library but are required at runtime through framework packages.

| Dependency | Source framework | Type | Usage |
| --- | --- | --- | --- |
| `Okta.AspNetCore` | `SAIF.NetCore.OIDC` | Transitive | Supplies the OIDC/Okta provider integration that OIDC's own registered services (composed by `RegisterOidcServices`) use for token exchange; this library never references the package directly*. |
| `Microsoft.AspNetCore.Authentication.JwtBearer` | `SAIF.NetCore.AuthService` | Transitive | Supplies `JwtBearerDefaults`/`AddJwtBearer`, used directly by `RegisterEdgeAPIAuthServices`/`RegisterCoreAPIAuthServices` without this library declaring its own `PackageReference` to the JwtBearer package*. |

## API dependencies

This library never makes an outbound HTTP call from its own code; every row below is reached indirectly, through calls this library's registration/middleware code makes into EnvironmentClient's own registered `IEnvironmentClient` implementation (`BaseEnvironmentClient`/`SecureEnvironmentClient`). Each row names the real remote endpoint, the call chain that reaches it, and the condition under which it actually fires. A key distinction runs through every row after the first: **calling the refresh check happens on every settings access** (ordinary settings via `LoadSection`/`LoadSectionAsync`, secure settings via a direct call from `RetrieveSecureSetting`/`RetrieveSecureSettingAsync`), but the check's three possible outcomes are not interchangeable: an **empty** settings cache reloads immediately, with **no** `GET /settings/lastRefreshed` probe at all (`TestForRefreshSettings` returns `true` before any network call); a **non-empty** cache before `TimeToWaitSeconds` (60s) have elapsed since the last probe makes **no** network call and does **not** reload; only a **non-empty** cache at or past that 60s throttle window actually issues the `GET /settings/lastRefreshed` probe, and even then a reload happens only if that call succeeds **and** the server's returned timestamp is newer than the client's current `LastRefreshed` — see [Refresh check and cache reload](../saif-netcore-environmentclient/operations/refresh-check-and-cache-reload.md) (`SAIF.NetCore.EnvironmentClient`) for the full three-branch detail.

| Endpoint | Method | Called from | Source library | Reached via | Usage |
| --- | --- | --- | --- | --- | --- |
| `/v2/tracks/{applicationName}/value` (Environment API) | GET | [Register environment client](operations/register-environment-client.md) / [Register secure environment client](operations/register-secure-environment-client.md) — on first resolution of the singleton `IEnvironmentClient` | `SAIF.NetCore.EnvironmentClient` (`src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`) — `BaseEnvironmentClient.Setup`/`GetHostsByTrackWithResilience`/`GetHostsByTrack` | `RegisterEnvironmentClient`/`RegisterSecureEnvironmentClient` register `IEnvironmentClient` as a singleton with no factory of this library's own; construction (and therefore `Setup()`) happens whenever the DI container first resolves the singleton, before any setting lookup. | Resolves the application's current track/host assignment so the client knows which Environment API host to address for every later setting lookup. |
| `/settings/lastRefreshed` (Environment API) | GET | Every settings access this library triggers: [Register DB connection client](operations/register-db-connection-client.md) step 4 (secure, via `GetSecureSetting`), [Legacy cookie gate middleware](operations/legacy-cookie-gate-middleware.md) step 2 (ordinary, via `GetSettingValueAsync`) | `SAIF.NetCore.EnvironmentClient` (`src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`) — `BaseEnvironmentClient.TestForRefreshSettings`/`CheckForUpdates` | `CheckForUpdates` runs on every `LoadSection`/`LoadSectionAsync` call (ordinary settings) and is also called directly by `RetrieveSecureSetting`/`RetrieveSecureSettingAsync` (secure settings) — **not** by routing the secure call through `LoadSection` first. An **empty** settings cache reloads the entire cache immediately with **no** GET at all (no probe needed on first access). A **non-empty** cache issues this GET only once `LastCheckForRefresh.AddSeconds(TimeToWaitSeconds) < DateTime.Now` (throttled to at most once per 60s); before that it returns immediately with no network call. When the probe does fire, a reload still only happens if the call succeeds **and** the returned `lastRefreshed` timestamp is newer than the client's current value — an elapsed interval alone does not force a reload. Whichever branch triggers a reload, `CheckForUpdates` clears and reloads the entire settings cache, including every previously-known section. See [Refresh check and cache reload](../saif-netcore-environmentclient/operations/refresh-check-and-cache-reload.md) (`SAIF.NetCore.EnvironmentClient`) for the full branch-by-branch detail. | Cheaply detects whether the server's configuration generation has changed since the client's last reload, so the ordinary- and secure-setting caches are invalidated and reloaded only when stale rather than on every call. |
| `/v2/secureSetting/{applicationName}/{track}/[{section}/]{configKey}/value?systemHashValue={hash}` (Environment API) | GET | [Register DB connection client](operations/register-db-connection-client.md) step 4, via `IEnvironmentClient.GetSecureSetting(key)` on first `IDBConnectionClient` resolution | `SAIF.NetCore.EnvironmentClient` (`src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs`) — `RetrieveSecureSetting`/`GetSecureSettingUrlPart` | `RetrieveSecureSetting`/`RetrieveSecureSettingAsync` call `CheckForUpdates()` directly first (see row above), then check the local `SecureSettingCache` keyed on `"{section}.{configKey}"`; on a cache miss it issues this GET (gated by `HasSetting` reporting the key exists). | Retrieves and decrypts the DB connection service account credentials (a pipe-delimited `username`/`password` pair) needed to bootstrap the DB connection client's NTLM authentication. |
| `/v2/settings/{applicationName}/{applicationHost}/[{section}/]value` (Environment API) | GET | [Legacy cookie gate middleware](operations/legacy-cookie-gate-middleware.md) step 2, via `IEnvironmentClient.GetSettingValueAsync("endpoint", "LegacySessionProvider")` in `AspNetSessionIdMiddleware.InvokeAsync`, and in `LegacyBolAuthMiddleware.InvokeAsync` if a consuming application manually adds that middleware to its pipeline (this library registers it in DI but never calls `UseMiddleware` for it itself). This same ordinary-settings pattern is also reached, for different keys, by `ConnectionClient`'s own constructor-time `Setup()` (invoked from [Register DB connection client](operations/register-db-connection-client.md) step 2), which reads the `DBConnectionClient` section's four settings (`ServiceUrl`, `SqlConnectionEndpoint`, `PostgreSqlConnectionEndpoint`, `OracleConnectionEndpoint`) via `IEnvironmentClient.GetSettingValue`. | `SAIF.NetCore.EnvironmentClient` (`src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`) — `GetSettingValueAsync`/`GetSettingValue`/`LoadSettingsAsync`/`LoadSettings`/`GetEnvironmentApiUrl` | `GetSettingValueAsync`/`GetSettingValue` call `LoadSectionAsync`/`LoadSection`, which run the refresh check above (and can reload an already-known section if the probe fires) and then issue this GET only if that section is not already recorded in the loaded-`Sections` cache. | For the middleware caller: looks up the legacy session provider's redirect endpoint path so the middleware can build the fail-open redirect URL sent to a request missing the legacy cookie. For the DB-client caller: resolves the DB connection service's base URL and per-database-engine connection endpoints needed before credentials can be applied. |

## Dependency risks and notes
- This library's `Architecture.md`-described role of letting an application "only need[ ] to add a reference to this single NuGet package to get access to all SAIF services" is only partially true: it directly references 10 of the other 18 libraries in this repository. Of the remainder, `HashUtil` is nonetheless transitively available (it arrives automatically via this library's direct reference to `SystemHash`, which itself directly references `HashUtil`), but `Common`, `Clients.UC4`, `LegacyAuth`, `NHibernate`, `NtlmApiAuth`, `Services.Messaging`, and `WindowsService` are not referenced at all, directly or transitively, and must be referenced separately by any consuming application that needs them*.
- `Microsoft.Extensions.DependencyInjection` stays pinned at `8.0.0` on the `net10.0` target framework while the other framework-adjacent packages (`Negotiate`, `Mvc.NewtonsoftJson`) are bumped to `10.0.12`, a version-skew inconsistency whose runtime implications were not independently verified**.
- The five `OpenTelemetry.*` packages are published to the public `nuget.org` feed rather than being internal SAIF packages, so their availability depends on external feed connectivity rather than the organization's own package infrastructure*.
- Enabling `ConfigureOpenTelemetry` replaces all existing logging providers with OTel's own; an application that also relies on `RegisterLoggingService`/`ConfigureSaifLogger` console output will lose it unless the application re-adds a console provider after enabling OTel*.
- Most internal references are composition-root wiring (constructing/registering the referenced package's own types with no further call from inside this library's own code). **EnvironmentClient is a documented exception**: `RegisterEnvironmentClient`/`RegisterSecureEnvironmentClient` register `IEnvironmentClient` as a singleton whose construction (and bootstrap track/host lookup) is deferred to first resolution; `RegisterDBConnectionClient`'s factory calls `IEnvironmentClient.GetSecureSetting(key)` on first `IDBConnectionClient` resolution; and `AspNetSessionIdMiddleware` calls `IEnvironmentClient.GetSettingValueAsync(key)` on every request with no session cookie — none of these are registration-only wiring (see [API dependencies](#api-dependencies) above).

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [Service registration](service-registration.md)
- [API reference](api/index.md)
- [SAIF.NetCore.ServiceContracts overview](../saif-netcore-servicecontracts/overview.md)
- [SAIF.NetCore.AuthService overview](../saif-netcore-authservice/overview.md)
- [SAIF.NetCore.OIDC overview](../saif-netcore-oidc/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.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions.csproj`
- `src/SAIF.NetCore.ServiceExtensions/Directory.Packages.props`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs`, `IAppBuilderExtensions.cs`, `IWebHostBuilderExtensions.cs`, `IHostBuilderExtensions.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/OpenTelemetryExtensions.cs`, `OpenTelemetryOptions.cs`
- `Architecture.md`
