---
title: Configuration settings
summary: Configuration keys consumed by this library, retrieval methods, and runtime dependencies.
status: draft
owner: SAIF Framework Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.WindowsService
reference_type: package
---

# Configuration settings

This library reads exactly one configuration key directly, `DelayStart`, through `IConfiguration`. Its larger configuration impact is architectural: it defines the configuration-source stack for every Windows Service built on `ServiceProgramRunnerBase`, re-appending `appsettings.json` and unprefixed environment variables after the Generic Host defaults, which inverts standard .NET precedence. At startup it also triggers the Environment API reads of the resolved `ISaifWindowsServiceLogService`, a transitive dependency.

## Configuration retrieval methods

| Method | Class / Interface | Namespace | Usage pattern |
| --- | --- | --- | --- |
| `IConfiguration.GetValue<int?>(key)` | `IConfiguration`, resolved from `IHost.Services` with `GetService` | `Microsoft.Extensions.Configuration` (+ `.Binder` 8.0.0) | A single imperative read of `DelayStart` in private `DelayStartup(IHost)`, called from `Create` after `Build()` and before `Configure`. |
| `Host.CreateDefaultBuilder(args)` | `Host` | `Microsoft.Extensions.Hosting` 8.0.0 | Registers the default host and app-configuration providers. |
| `IConfigurationBuilder.AddJsonFile("appsettings.json", optional: true)` | `IConfigurationBuilder` | `Microsoft.Extensions.Configuration.Json` (transitive) | Appends a second, non-reloading `appsettings.json` provider after the Generic Host defaults. |
| `IConfigurationBuilder.AddEnvironmentVariables()` | `IConfigurationBuilder` | `Microsoft.Extensions.Configuration.EnvironmentVariables` (transitive) | Appends a second, unprefixed environment-variable provider; it is the last provider added, so it has the highest precedence. |
| `IHostBuilder.UseWindowsService()` | `IHostBuilder` | `Microsoft.Extensions.Hosting.WindowsServices` 8.0.0 | Enables the Windows Service lifetime; when running under the SCM, also sets the content root to `AppContext.BaseDirectory`. |
| `ISaifWindowsServiceLogService.Configure(IEnvironmentClient, IHttpJsonClient)` (indirect) | `ServiceProgramRunnerBase.ConfigureLogger` (virtual) | `SAIF.NetCore.Services.Contracts` | Resolves `IEnvironmentClient` and hands it to the logger, which then makes the Environment API reads listed under Transitive configuration. |

## appsettings.json and IConfiguration

Settings read through `IConfiguration` (`appsettings.json`, `appsettings.<Environment>.json`, environment variables, and any other configured provider).

| Setting key | Required | Default value | Retrieval | Purpose | Consuming class | Source |
| --- | --- | --- | --- | --- | --- | --- |
| `DelayStart` | No | — (absent → no delay) | `GetValue<int?>` | Blocks startup for the given number of milliseconds, after the host is built and before the error-handler/logger hooks and `IHost.Run()`; intended to give a developer time to attach a debugger to an installed service. `0` is no delay; `-1` blocks forever (`Timeout.Infinite`); any other negative value throws `ArgumentOutOfRangeException`; a non-integer string throws `InvalidOperationException`. | `ServiceProgramRunnerBase.DelayStartup` (private, non-overridable) | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

**`DelayStart` can be supplied by:** an `appsettings.json` entry, the environment variable `DelayStart` (highest precedence, because of the re-added provider), or a process command-line argument (`--DelayStart=<value>`, `/DelayStart=<value>`, or `DelayStart=<value>`). For an installed service, the command-line form means the SCM `binPath` arguments, **not** the Services console "Start parameters" field — the README documents the wrong mechanism and the wrong key name (`DelayStartup`), so following it produces no delay*.

**Configuration source stack defined by this library.** `CreateHostBuilder` re-appends two providers after the `Host.CreateDefaultBuilder` defaults (host configuration, `appsettings.json`, `appsettings.{Environment}.json`, user secrets in Development, environment variables, command-line args):

| Order | Provider | Added by |
| --- | --- | --- |
| 6 | `appsettings.json` (optional, `reloadOnChange: false`) | `ServiceProgramRunnerBase.CreateHostBuilder` |
| 7 | Environment variables (unprefixed) | `ServiceProgramRunnerBase.CreateHostBuilder` |

Resulting precedence (highest first): **environment variables > `appsettings.json` > command line > user secrets > `appsettings.{Environment}.json`***. Consequences: `appsettings.{Environment}.json` is effectively shadowed for any key also present in `appsettings.json`; command-line `binPath` arguments lose to `appsettings.json` and to environment variables; hot reload of `appsettings.json` is masked by the non-reloading duplicate provider, since provider 1 reloads but provider 6 (same file) does not*.

**Code-default tunables (not configuration-bound).** `ServiceWorker<T>.TaskDelayMilliseconds` (default `3000`) and `ServiceWorker<T>.ShutdownTimeoutSeconds` (default `30`) are public settable properties with code defaults; consumers may set them from an Environment API value in their own worker constructor, but neither is bound to a configuration key by this library.

## Environment variables

| Variable | Scope read | Accepted values | Default when unset | Effect | Source |
| --- | --- | --- | --- | --- | --- |
| `DelayStart` | Process (inherited from machine/service-account environment) | Integer milliseconds | No delay | Startup delay; highest precedence of all `DelayStart` sources because of the re-added provider. | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |
| `DOTNET_ENVIRONMENT` | Process | `Development`, `Staging`, `Production`, or any name | `Production` | Selects `appsettings.{Environment}.json` (largely shadowed, see above) and enables user secrets / DI scope validation in `Development`. | Microsoft.Extensions.Hosting `CreateDefaultBuilder` (framework-standard) |
| Any unprefixed `Section__Key` | Process | — | — | Overrides the same key from every other source (`__` maps to `:`); applies to every consumer and transitive `IConfiguration` key, including `COREAPIENABLED` and `AppSettings__ApplicationHost`. | `ServiceProgramRunnerBase.CreateHostBuilder` (re-added `AddEnvironmentVariables()`) |

## Database connection configuration

Not applicable. The library makes no connection reads and resolves no `IDBConnectionClient` directly. See Transitive configuration for the one connection credential the companion registration can require.

## Secure settings

Not read directly by this library. For completeness, the companion registration (`SAIF.NetCore.ServiceExtensions.RegisterForWindowsService<T>`) always calls `RegisterDBConnectionClient()`, which registers `IDBConnectionClient` as a lazy singleton factory. That factory — not `ConnectionClient` itself — performs the secure read the first time a consumer resolves `IDBConnectionClient`:

| Setting key | Encryption method | Purpose | Consuming class | Source |
| --- | --- | --- | --- | --- |
| `dbconnectionclient:connectionservicecreds` | `IEnvironmentClient.GetSecureSetting("connectionservicecreds", "dbconnectionclient", hash)`, where `hash` comes from a `SystemHashService` instance the factory constructs directly (`new SystemHashService(typeof(ConnectionClient).Assembly)`, **not** DI-resolved) and calls `.Get(SystemType.WebApp)` on (hard-coded `SystemType.WebApp`, even for Windows Services*) | Database connection credentials for `IDBConnectionClient`; the factory splits the returned string on `\|` and calls `ConnectionClient.SetupClientCredentials(credentials[0], credentials[1])` | `RegisterDBConnectionClient` (the factory lambda, in `SAIF.NetCore.ServiceExtensions`) -- `ConnectionClient` only receives the already-resolved credentials through `SetupClientCredentials` and does not perform the read itself | src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs; src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs |

## Transitive configuration (from dependencies)

Settings required at runtime by services this library resolves but does not implement. These keys apply when the standard companion registration (`SAIF.NetCore.ServiceExtensions.RegisterForWindowsService`/`RegisterForWindowsService<T>`) supplies `IWindowsServiceErrorHandler`, `ISaifWindowsServiceLogService`, `IEnvironmentClient`, and `IHttpJsonClient` with their shipped implementations; a consumer who registers an equivalent, manually-implemented set of those four contracts instead may read a different set of settings, or none of these, depending on that implementation.

| Setting key | Method | Source dependency | Dependency type | Purpose |
| --- | --- | --- | --- | --- |
| `tracingdefaults:loggingapiurl` | `IEnvironmentClient.GetSettingValue` | `SAIF.NetCore.Logging` | NuGet Package (same-repo) | Logging API base URL, read by `SaifWindowsServiceLog.Configure`, which this library's `ConfigureLogger` hook invokes. |
| `tracingdefaults:logroute` | `IEnvironmentClient.GetSettingValue` | `SAIF.NetCore.Logging` | NuGet Package (same-repo) | Logging API route, read by the same call. |
| `{ApplicationName}:logthreshold` → fallback `tracingdefaults:logthreshold` | `IEnvironmentClient.HasSetting`/`GetSettingValue` (read independently in two places: `SaifLogBase`'s own `LogThreshold` property, and again inside `ApiLoggerConfig`'s constructor for the API-sink-specific level) | `SAIF.NetCore.Logging` | NuGet Package (same-repo) | Minimum log level threshold. |
| `{ApplicationName}:netcorelogpattern` → fallback `tracingdefaults:netcorelogpattern` | `IEnvironmentClient.HasSetting`/`GetSettingValue`, read inside `ApiLoggerConfig`'s constructor -- `ApiLoggerConfig` and the base `Pattern`/`Level`/`LoggerType` properties it populates are both declared on `SaifLoggerConfig` (`SaifLoggerConfig.cs`), a separate class from the logging-service base `SaifLogBase` | `SAIF.NetCore.Logging` | NuGet Package (same-repo) | Output template pattern for the API logging sink. |
| `tracingdefaults:logerrorroute` | — (not read for Windows Services) | `SAIF.NetCore.Logging` | NuGet Package (same-repo) | Would be the error-specific logging route; `SaifWindowsServiceLog` never reads it, so `LoggingApiErrorRoute` stays `null`*. |
| `IEnvironmentClient.TrackName` / `.ApplicationName` | Client properties | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Composes the EventLog source name; an empty `TrackName` throws on `.First()`*. |
| `AppSettings:ApplicationHost` | `IConfiguration` bootstrap | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Required on developer machines to resolve `IEnvironmentClient`. |
| `AppSettings:EnvApiDeveloperOverrideAllowed` | `IConfiguration` bootstrap | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Optional; default `true`, set `false` on non-domain-joined servers. |
| `AppSettings:EnvApiDeveloperSettingsOverride` | `IConfiguration` bootstrap | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Optional; developer-only settings override file name. |
| `EnvironmentClient:GetHostsByTrack:RetryCount` | `IConfiguration` bootstrap (`GetHostsByTrackConfig`) | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Optional bootstrap-resilience setting; number of retry attempts for `GetHostsByTrack` calls (default `3`). Not read directly by this library -- it is read only inside `IEnvironmentClient`'s own implementation. |
| `EnvironmentClient:GetHostsByTrack:RetrySleepMs` | `IConfiguration` bootstrap (`GetHostsByTrackConfig`) | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Optional bootstrap-resilience setting; milliseconds to sleep between `GetHostsByTrack` retries (default `3000`). Not read directly by this library. |
| `COREAPIENABLED` | `IConfiguration` (unprefixed only; `ASPNETCORE_COREAPIENABLED` is not mapped in this non-web host*) | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Required for the secure environment client on servers. |
| `EnvironmentApiUrl` | `Environment.GetEnvironmentVariable` (bypasses the `IConfiguration` stack) | `SAIF.NetCore.EnvironmentClient` | NuGet Package (same-repo) | Required; cannot be supplied by `appsettings.json` or the command line. |
| `dbconnectionclient:connectionservicecreds` | `IEnvironmentClient.GetSecureSetting`, secure | `SAIF.NetCore.ServiceExtensions`'s `RegisterDBConnectionClient` factory lambda (performs the read itself with a self-constructed `SystemHashService`, not an injected one) | NuGet Package (same-repo) | Required only if a consumer resolves `IDBConnectionClient`. The read and the factory both live in `SAIF.NetCore.ServiceExtensions`; `ConnectionClient` (in `SAIF.NetCore.DBConnectionClient`) only receives the resulting credentials via `SetupClientCredentials` and does not read the secure setting. On the non-secure `RegisterEnvironmentClient()` path, this call throws `EnvironmentClientException` ("Operation not allowed! This application is restricted from retrieving secure settings.") rather than failing with a null hash service. |

**Startup ordering note:** `DelayStart` is read **before** `ConfigureLogger` resolves `IEnvironmentClient`, so a configured delay also postpones every transitive read above and the Environment API HTTP calls they trigger — useful when attaching a debugger to step through environment-client bootstrap.

## Configuration dependencies

| Dependency | Type | Required at | Impact if missing |
| --- | --- | --- | --- |
| `IConfiguration` | service (always registered by the Generic Host) | startup (`DelayStartup`) | Not expected to be missing; a `null` would throw `NullReferenceException`. |
| `IWindowsServiceErrorHandler`, `ISaifWindowsServiceLogService`, `IEnvironmentClient`, `IHttpJsonClient` | services (resolved with `GetRequiredService`) | startup (`Configure`/`ConfigureLogger`) | `InvalidOperationException` if any is not registered; the recommended way to supply them is `SAIF.NetCore.ServiceExtensions.RegisterForWindowsService`/`RegisterForWindowsService<T>`, though `ServiceProgramRunnerBase` only requires the four contracts to be resolvable — an equivalent manual registration satisfies the same requirement without that call. |
| `tracingdefaults:loggingapiurl`, `tracingdefaults:logroute`, non-empty `TrackName` | Environment API settings (transitive) | startup (`ConfigureLogger`) | Startup fails inside `SaifWindowsServiceLog.Configure` before the service runs. |
| `EnvironmentApiUrl` | environment variable (transitive) | startup | `IEnvironmentClient` resolution fails before `ConfigureLogger` can run. |

The README documents a debugging technique (`/DelayStartup=[ms]` as a Services-console start parameter) that does not work: the code reads `DelayStart`, not `DelayStartup`, and Windows Service start parameters likely do not reach process `Main(string[] args)` at all*.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Service registration](service-registration.md)
- [API reference](api/index.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.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs`
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs`
- `src/SAIF.NetCore.WindowsService/README.md` (documents the obsolete `/DelayStartup=[ms]` Services-console parameter and the wrong key name)
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifWindowsServiceLog.cs` (logging sink/setting reads triggered by `ConfigureLogger`)
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifLogBase.cs` (base-class `LogThreshold` resolution via its own independent `logthreshold` read, and logger-override application; declares the abstract `BuildStandardLoggerConfigs`/`InitializeLoggerInfoLookups` that populate `Patterns` from each `SaifLoggerConfig` subclass)
- `src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SaifLoggerConfig.cs` (`ApiLoggerConfig`'s constructor is the class that actually reads `{ApplicationName}:netcorelogpattern`/`tracingdefaults:netcorelogpattern` and the per-sink `logthreshold` fallback; `EventLogConfig` uses a fixed `DefaultPattern` instead and reads no setting)
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`, `SecureEnvironmentClient.cs` (settings resolution behind `IEnvironmentClient`)
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/GetHostsByTrackConfig.cs` (`RetryCount`/`RetrySleepMs` defaults)
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs` (`RegisterDBConnectionClient` factory: constructs its own `SystemHashService`, performs the secure credential read, and calls `ConnectionClient.SetupClientCredentials`)
- `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` (`IDBConnectionClient` implementation; receives credentials via `SetupClientCredentials` but does not itself perform the secure-setting read)
