Lyo.Config.Api.Hosting
1.0.2
dotnet add package Lyo.Config.Api.Hosting --version 1.0.2
NuGet\Install-Package Lyo.Config.Api.Hosting -Version 1.0.2
<PackageReference Include="Lyo.Config.Api.Hosting" Version="1.0.2" />
<PackageVersion Include="Lyo.Config.Api.Hosting" Version="1.0.2" />
<PackageReference Include="Lyo.Config.Api.Hosting" />
paket add Lyo.Config.Api.Hosting --version 1.0.2
#r "nuget: Lyo.Config.Api.Hosting, 1.0.2"
#:package Lyo.Config.Api.Hosting@1.0.2
#addin nuget:?package=Lyo.Config.Api.Hosting&version=1.0.2
#tool nuget:?package=Lyo.Config.Api.Hosting&version=1.0.2
Lyo.Config.Api.Hosting
Bridges IConfigApiClient (Lyo.Config.Api.Client) into Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Options: a BackgroundService keeps a shared ResolvedConfigRecord ledger (ETags + 304 polling), then one definition key JSON blob binds each IOptionsMonitor<TOptions>.
Prefer IOptionsMonitor<TOptions>.CurrentValue (or OnChange) for values that reload at runtime. IOptions<TOptions> is not registered here and would not observe remote updates anyway.
Examples
Registration order
using Lyo.Config.Api.Client;
using Lyo.Config.Api.Hosting;
// 1 — HTTP client (`BaseUrl`, optional `ApiKey`)
services.AddConfigApiClientFromConfiguration(configuration);
// 2 — ledger + polling (binds configuration section defaults below)
services.AddConfigApiPolling(configuration);
// 3 — one registrant per POCO keyed by Config API definition Key
services.AddConfigApiOptions<MyFeatureOptions>(
definitionKey: "myFeature",
missingDefinitionKeyBehavior: ConfigApiMissingDefinitionKeyBehavior.Throw);
Configuration
{
"ConfigApi": {
"BaseUrl": "http://localhost:5088/",
"ApiKey": ""
},
"ConfigApiPolling": {
"Enabled": true,
"AppKind": "worker",
"AppId": "image-processor",
"DelayWhenNotModified": "00:00:15",
"StartupTimeout": "00:05:00",
"RequireSuccessOnStartup": true
}
}
Registration order
Reference the project Lyo.Config.Api.Hosting from your worker/API host (Microsoft.Extensions.Hosting is assumed).
Configuration
StartupTimeout— omit ornullto wait indefinitely for the first 200.RequireSuccessOnStartup—falseallows the host to start afterStartupTimeouteven if no snapshot arrived (ledger stays empty unless you later reload manually; prefer keepingtrueunless you tolerate cold-start without remote config).
Polling enabled vs disabled
ConfigApiPollingOptions.Enabled (defaults to true) gates the entire background poller:
Enabled |
ConfigApiPollingHostedService behavior |
ConfigApiResolvedLedger.Current at startup |
Behavior of IOptionsMonitor<T> from AddConfigApiOptions<T> |
|---|---|---|---|
true |
StartAsync validates AppKind / AppId and blocks until the first 200 (or StartupTimeout if set). ExecuteAsync then loops ResolveForAppAsync with the latest ETag and DelayWhenNotModified. Errors retry after fixed back-offs and are logged. |
Populated with the first successful payload after StartAsync returns. |
CurrentValue returns the bound TOptions. OnChange fires after each ledger swap. |
false |
StartAsync skips validation and the first probe; ExecuteAsync returns immediately. The hosted service stays in the DI container but does not touch the network. No ledger updates from the network ever occur. Some other code path (typically tests) may still call ConfigApiResolvedLedger.SetResolved manually. |
null unless something else calls SetResolved directly. |
Materialization sees a null ledger and falls back to ConfigApiMissingDefinitionKeyBehavior: Throw raises InvalidOperationException; UseDefaultInstance returns new TOptions(). |
The polling service performs the only writes to
ConfigApiResolvedLedgerin production. WithEnabled == false, every options monitor is effectively driven bymissingDefinitionKeyBehavior. Use that mode for test hosts or for services that consume config exclusively via REST.
ConfigApiResolvedLedger — the in-process resolved-config cache
ConfigApiResolvedLedger is the shared in-process snapshot that ties the background poller to all IOptionsMonitor<T> instances. It is
registered as a singleton by AddConfigApiPolling (and also TryAddSingleton-ed by AddConfigApiOptions<T>, so you can register options without polling).
| Surface | Purpose |
|---|---|
ResolvedConfigRecord? Current |
Latest payload. null until the first successful resolve. Thread-safe getter (lock-protected). |
string? CurrentEtag |
Opaque ETag from the last successful 200 response. Passed back to the server as If-None-Match on every subsequent probe. |
IChangeToken GetReloadToken() |
Returns a CancellationChangeToken invalidated on the next swap. Used by ConfigApiOptionsMonitor<T> to rebuild its cached TOptions. |
void SetResolved(ResolvedConfigRecord resolved, string? etag) |
Atomically updates Current + CurrentEtag, cancels the previous reload token (notifying every subscriber), and disposes the old CancellationTokenSource. |
ConfigApiOptionsMonitor<T> subscribes to GetReloadToken via ChangeToken.OnChange, so every successful ledger swap triggers re-materialization of all registered options
types in lock-step.
Missing definition key behaviour
Materialization runs through ConfigApiResolvedLedger.Current.TryGetValue(definitionKey, …) and then configValue.GetValue<T>(ConfigJsonSerializerOptions.Default). The
ConfigApiMissingDefinitionKeyBehavior selected at registration controls the failure modes:
| Condition | Throw (default) |
UseDefaultInstance |
|---|---|---|
Ledger is empty (Current == null). |
InvalidOperationException("No Config API snapshot is available yet (ledger empty). …") |
new TOptions() |
Definition key absent from the resolved payload, or its JSON value is null. |
InvalidOperationException("Definition key '<key>' is missing from resolved Config API payload …") |
new TOptions() |
Key present, JSON not assignable to TOptions (deserialize returns null). |
InvalidOperationException("JSON for definition key '<key>' did not deserialize to <TOptions>.") |
new TOptions() |
Choose UseDefaultInstance for features that may have no bindings yet; keep Throw for required configuration so misconfiguration fails fast at startup.
Limitations
- No named options.
ConfigApiOptionsMonitor<T>ignores thenameargument onIOptionsMonitor<T>.Get(string?): requests forOptions.DefaultName(ornull/ empty) return the cachedCurrentValue, anything else throwsInvalidOperationException. There is no way to map several names onto different definition keys through this monitor. - One definition key per
TOptionstype per host.AddConfigApiOptions<TOptions>(definitionKey, …)registers an unkeyed singletonIOptionsMonitor<TOptions>. Calling it twice with the sameTOptionsand differentdefinitionKeyvalues replaces the prior registration (last call wins); you cannot bind one POCO to two definition keys in the same host. Use distinctTOptionstypes (or sub-records) for each definition key. Enabled = falseleaves the ledger empty. Without polling,ConfigApiResolvedLedger.Currentstaysnullunless something else callsSetResolveddirectly. Options monitors then fall back toConfigApiMissingDefinitionKeyBehaviorsemantics — see the table above.IOptions<T>andIOptionsSnapshot<T>are not registered by this package. InjectIOptionsMonitor<T>(or, in scoped consumers,IOptionsSnapshot<T>if you register your own adapter) so changes from the polling service propagate.
Runtime behaviour summary
flowchart LR
HS[BackgroundService_poll] --> Ledger[Resolved_ledger_ETag]
Ledger --> Mon[OptionsMonitor_builder]
- Hosted service probes
IConfigApiClient.ResolveForAppAsyncwithAppKind/AppIdandIf-None-MatchfromConfigApiResolvedLedger.CurrentEtag. - On 200, the ledger swaps in the new
ResolvedConfigRecordand invalidatesIChangeTokenlisteners. - Each
ConfigApiOptionsMonitor<T>reacts by re-materializingTviaResolvedConfigRecord.TryGetValue(definitionKey, …)+ JSON deserialize (same *ConfigJsonSerializerOptions.Default* semantics asGetValue<T>elsewhere).
REST paths remain in ../Lyo.Config.Api/README.md. Resolve outcomes (ConfigResolveOutcome) are in *
*Lyo.Config.Api.Models**; HTTP registration and ConfigPolling in Lyo.Config.Api.Client.
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Config.Api.Client— (direct, lyo)Microsoft.Extensions.Hosting10.0.5— (direct, microsoft)Microsoft.Extensions.Options.ConfigurationExtensions10.0.5— (direct, microsoft)Lyo.Api.Client— (transitive, lyo)Lyo.Api.Models— (transitive, lyo)Lyo.Common— (transitive, lyo)Lyo.Config— (transitive, lyo)Lyo.Config.Api.Models— (transitive, lyo)Lyo.DateAndTime— (transitive, lyo)Lyo.Diagnostic— (transitive, lyo)Lyo.EntityReference.Models— (transitive, lyo)Lyo.Exceptions— (transitive, lyo)Lyo.Hashing— (transitive, lyo)Lyo.PackageMetadata— (transitive, lyo)Lyo.Query.Models— (transitive, lyo)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5— (transitive, microsoft)Microsoft.Extensions.Http10.0.5— (transitive, microsoft)Microsoft.Extensions.Logging.Abstractions10.0.5— (transitive, microsoft)System.IO.Hashing10.0.5— (transitive, microsoft, net10.0)System.Memory4.6.3— (transitive, microsoft, netstandard2.0)System.Text.Json10.0.5— (transitive, microsoft, netstandard2.0)System.Threading.Tasks.Extensions4.6.3— (transitive, microsoft)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Lyo.Config.Api.Client (>= 1.0.2)
- Microsoft.Extensions.Hosting (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
-
net10.0
- Lyo.Config.Api.Client (>= 1.0.2)
- Microsoft.Extensions.Hosting (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.