# ZB.MOM.WW.Configuration Startup configuration-validation library for the **ZB.MOM.WW SCADA family** (OtOpcUa, MxAccessGateway, ScadaBridge). This is a **library, not a service** — the package is linked directly into the consuming application at build time. It extracts the `IValidateOptions` plumbing the three apps share — failure accumulation, rule primitives, bind+validate DI wiring, and pre-host preflight — so that domain-specific validation rules stay per-project and the boilerplate does not drift. --- ## What's in the box | Type | Description | |---|---| | `OptionsValidatorBase` | Abstract `IValidateOptions`. Override `protected void Validate(ValidationBuilder v, TOptions o)` to declare failures; the base aggregates all failures and returns a single `ValidateOptionsResult`. | | `ValidationBuilder` | Failure accumulator. Primitives: `Required`, `Port`, `HostPort`, `PositiveTimeSpan`, `OneOf`, `MinCount`, `RequireThat(bool, msg)`, `Add(msg)`. Properties: `Failures` (read), `IsValid`. | | `ServiceCollectionExtensions` | `AddValidatedOptions(IConfiguration config, string sectionPath)` — binds the section, registers the validator, and calls `ValidateOnStart()` in a single extension method. Returns `OptionsBuilder`. | | `ConfigPreflight` | Pre-host raw-`IConfiguration` checker. Fluent API: `For(config)`, `.Require(key, predicate, reason)`, `.RequireValue(key)`, `.RequirePort(key)`, `.When(cond, block)`, `.ThrowIfInvalid()`. | --- ## Usage ### 1. Validator subclass ```csharp public sealed class ClusterOptionsValidator : OptionsValidatorBase { protected override void Validate(ValidationBuilder v, ClusterOptions o) { v.MinCount(o.SeedNodes, 2, "Cluster:SeedNodes"); v.OneOf(o.Strategy, new[] { "keep-oldest" }, "Cluster:Strategy"); v.PositiveTimeSpan(o.StableAfter, "Cluster:StableAfter"); } } ``` ### 2. DI wiring ```csharp builder.Services.AddValidatedOptions( builder.Configuration, "ScadaBridge:Cluster"); ``` This binds `ScadaBridge:Cluster`, registers `ClusterOptionsValidator`, and enables `ValidateOnStart` — the app refuses to start if the section fails validation. ### 3. Pre-host preflight ```csharp ConfigPreflight.For(configuration) .Require("Node:Role", v => v is "Central" or "Site", "must be 'Central' or 'Site'") .RequirePort("Node:RemotingPort") .When(role == "Site", p => p.RequireValue("Node:SiteId")) .ThrowIfInvalid(); ``` Use `ConfigPreflight` before `WebApplication.CreateBuilder` for critical keys (node role, remoting port, site ID) that must be present and valid before the DI container is even constructed. --- ## Building and testing ```bash # from ZB.MOM.WW.Configuration/ dotnet test ZB.MOM.WW.Configuration.slnx ``` All tests run with no external dependencies: | Assembly | Tests | |---|---| | `ZB.MOM.WW.Configuration.Tests` | 27 | | **Total** | **27** | --- ## Packing ```bash dotnet pack ZB.MOM.WW.Configuration.slnx -c Release -o ./artifacts ``` Produces one `.nupkg` file in `artifacts/`: ``` ZB.MOM.WW.Configuration.0.1.0.nupkg ``` `GeneratePackageOnBuild` is off — pack explicitly as above. Version is set in `Directory.Build.props`. --- ## Dependencies The package has a minimal closure — only `Microsoft.Extensions.*` abstractions: - `Microsoft.Extensions.Options` - `Microsoft.Extensions.Options.ConfigurationExtensions` - `Microsoft.Extensions.Configuration.Abstractions` - `Microsoft.Extensions.DependencyInjection.Abstractions` No third-party packages; no ASP.NET Core framework reference. --- ## Status **Built at 0.1.0. Adopted across all three apps on 2026-06-01** (local default branches; not yet pushed to remotes). Adoption is tracked in the component backlog: - `~/Desktop/scadaproj/components/configuration/GAPS.md` Design documentation lives alongside that backlog: - `~/Desktop/scadaproj/components/configuration/spec/SPEC.md` — normalized validation target - `~/Desktop/scadaproj/components/configuration/shared-contract/ZB.MOM.WW.Configuration.md` — proposed API - `~/Desktop/scadaproj/components/configuration/current-state/` — per-project current state (code-verified)